Merge branch 'feat/short' into lev-exchange

This commit is contained in:
Sam Germain
2021-09-04 20:15:36 -06:00
95 changed files with 1757 additions and 1148 deletions

View File

@@ -433,11 +433,11 @@ class FreqtradeBot(LoggingMixin):
if ((bid_check_dom.get('enabled', False)) and
(bid_check_dom.get('bids_to_ask_delta', 0) > 0)):
if self._check_depth_of_market_buy(pair, bid_check_dom):
return self.execute_buy(pair, stake_amount, buy_tag=buy_tag)
return self.execute_entry(pair, stake_amount, buy_tag=buy_tag)
else:
return False
return self.execute_buy(pair, stake_amount, buy_tag=buy_tag)
return self.execute_entry(pair, stake_amount, buy_tag=buy_tag)
else:
return False
@@ -465,8 +465,8 @@ class FreqtradeBot(LoggingMixin):
logger.info(f"Bids to asks delta for {pair} does not satisfy condition.")
return False
def execute_buy(self, pair: str, stake_amount: float, price: Optional[float] = None,
forcebuy: bool = False, buy_tag: Optional[str] = None) -> bool:
def execute_entry(self, pair: str, stake_amount: float, price: Optional[float] = None,
forcebuy: bool = False, buy_tag: Optional[str] = None) -> bool:
"""
Executes a limit buy for the given pair
:param pair: pair for which we want to create a LIMIT_BUY
@@ -479,7 +479,13 @@ class FreqtradeBot(LoggingMixin):
buy_limit_requested = price
else:
# Calculate price
buy_limit_requested = self.exchange.get_rate(pair, refresh=True, side="buy")
proposed_buy_rate = self.exchange.get_rate(pair, refresh=True, side="buy")
custom_entry_price = strategy_safe_wrapper(self.strategy.custom_entry_price,
default_retval=proposed_buy_rate)(
pair=pair, current_time=datetime.now(timezone.utc),
proposed_rate=proposed_buy_rate)
buy_limit_requested = self.get_valid_price(custom_entry_price, proposed_buy_rate)
if not buy_limit_requested:
raise PricingError('Could not determine buy price.')
@@ -743,7 +749,7 @@ class FreqtradeBot(LoggingMixin):
trade.stoploss_order_id = None
logger.error(f'Unable to place a stoploss order on exchange. {e}')
logger.warning('Selling the trade forcefully')
self.execute_sell(trade, trade.stop_loss, sell_reason=SellCheckTuple(
self.execute_trade_exit(trade, trade.stop_loss, sell_reason=SellCheckTuple(
sell_type=SellType.EMERGENCY_SELL))
except ExchangeError:
@@ -861,7 +867,7 @@ class FreqtradeBot(LoggingMixin):
if should_sell.sell_flag:
logger.info(f'Executing Sell for {trade.pair}. Reason: {should_sell.sell_type}')
self.execute_sell(trade, sell_rate, should_sell)
self.execute_trade_exit(trade, sell_rate, should_sell)
return True
return False
@@ -943,7 +949,7 @@ class FreqtradeBot(LoggingMixin):
was_trade_fully_canceled = False
# Cancelled orders may have the status of 'canceled' or 'closed'
if order['status'] not in ('cancelled', 'canceled', 'closed'):
if order['status'] not in constants.NON_OPEN_EXCHANGE_STATES:
filled_val = order.get('filled', 0.0) or 0.0
filled_stake = filled_val * trade.open_rate
minstake = self.exchange.get_min_pair_stake_amount(
@@ -959,7 +965,7 @@ class FreqtradeBot(LoggingMixin):
# Avoid race condition where the order could not be cancelled coz its already filled.
# Simply bailing here is the only safe way - as this order will then be
# handled in the next iteration.
if corder.get('status') not in ('cancelled', 'canceled', 'closed'):
if corder.get('status') not in constants.NON_OPEN_EXCHANGE_STATES:
logger.warning(f"Order {trade.open_order_id} for {trade.pair} not cancelled.")
return False
else:
@@ -981,7 +987,7 @@ class FreqtradeBot(LoggingMixin):
# if trade is partially complete, edit the stake details for the trade
# and close the order
# cancel_order may not contain the full order dict, so we need to fallback
# to the order dict aquired before cancelling.
# to the order dict acquired before cancelling.
# we need to fall back to the values from order if corder does not contain these keys.
trade.amount = filled_amount
trade.stake_amount = trade.amount * trade.open_rate
@@ -1062,9 +1068,9 @@ class FreqtradeBot(LoggingMixin):
raise DependencyException(
f"Not enough amount to sell. Trade-amount: {amount}, Wallet: {wallet_amount}")
def execute_sell(self, trade: Trade, limit: float, sell_reason: SellCheckTuple) -> bool:
def execute_trade_exit(self, trade: Trade, limit: float, sell_reason: SellCheckTuple) -> bool:
"""
Executes a limit sell for the given trade and limit
Executes a trade exit for the given trade and limit
:param trade: Trade instance
:param limit: limit rate for the sell order
:param sell_reason: Reason the sell was triggered
@@ -1080,6 +1086,17 @@ class FreqtradeBot(LoggingMixin):
and self.strategy.order_types['stoploss_on_exchange']:
limit = trade.stop_loss
# set custom_exit_price if available
proposed_limit_rate = limit
current_profit = trade.calc_profit_ratio(limit)
custom_exit_price = strategy_safe_wrapper(self.strategy.custom_exit_price,
default_retval=proposed_limit_rate)(
pair=trade.pair, trade=trade,
current_time=datetime.now(timezone.utc),
proposed_rate=proposed_limit_rate, current_profit=current_profit)
limit = self.get_valid_price(custom_exit_price, proposed_limit_rate)
# First cancelling stoploss on exchange ...
if self.strategy.order_types.get('stoploss_on_exchange') and trade.stoploss_order_id:
try:
@@ -1129,7 +1146,7 @@ class FreqtradeBot(LoggingMixin):
trade.close_rate_requested = limit
trade.sell_reason = sell_reason.sell_reason
# In case of market sell orders the order can be closed immediately
if order.get('status', 'unknown') == 'closed':
if order.get('status', 'unknown') in ('closed', 'expired'):
self.update_trade_state(trade, trade.open_order_id, order)
Trade.commit()
@@ -1368,7 +1385,9 @@ class FreqtradeBot(LoggingMixin):
if fee_currency:
# fee_rate should use mean
fee_rate = sum(fee_rate_array) / float(len(fee_rate_array)) if fee_rate_array else None
trade.update_fee(fee_cost, fee_currency, fee_rate, order.get('side', ''))
if fee_rate is not None and fee_rate < 0.02:
# Only update if fee-rate is < 2%
trade.update_fee(fee_cost, fee_currency, fee_rate, order.get('side', ''))
if not isclose(amount, order_amount, abs_tol=constants.MATH_CLOSE_PREC):
logger.warning(f"Amount {amount} does not match amount {trade.amount}")
@@ -1379,3 +1398,26 @@ class FreqtradeBot(LoggingMixin):
amount=amount, fee_abs=fee_abs)
else:
return amount
def get_valid_price(self, custom_price: float, proposed_price: float) -> float:
"""
Return the valid price.
Check if the custom price is of the good type if not return proposed_price
:return: valid price for the order
"""
if custom_price:
try:
valid_custom_price = float(custom_price)
except ValueError:
valid_custom_price = proposed_price
else:
valid_custom_price = proposed_price
cust_p_max_dist_r = self.config.get('custom_price_max_distance_ratio', 0.02)
min_custom_price_allowed = proposed_price - (proposed_price * cust_p_max_dist_r)
max_custom_price_allowed = proposed_price + (proposed_price * cust_p_max_dist_r)
# Bracket between min_custom_price_allowed and max_custom_price_allowed
return max(
min(valid_custom_price, max_custom_price_allowed),
min_custom_price_allowed)