diff --git a/freqtrade/enums/rpcmessagetype.py b/freqtrade/enums/rpcmessagetype.py index 9c59f6108..34b826ec9 100644 --- a/freqtrade/enums/rpcmessagetype.py +++ b/freqtrade/enums/rpcmessagetype.py @@ -5,13 +5,23 @@ class RPCMessageType(Enum): STATUS = 'status' WARNING = 'warning' STARTUP = 'startup' + BUY = 'buy' BUY_FILL = 'buy_fill' BUY_CANCEL = 'buy_cancel' + SELL = 'sell' SELL_FILL = 'sell_fill' SELL_CANCEL = 'sell_cancel' + SHORT = 'short' + SHORT_FILL = 'short_fill' + SHORT_CANCEL = 'short_cancel' + + EXIT_SHORT = 'exit_short' + EXIT_SHORT_FILL = 'exit_short_fill' + EXIT_SHORT_CANCEL = 'exit_short_cancel' + def __repr__(self): return self.value diff --git a/freqtrade/enums/signaltype.py b/freqtrade/enums/signaltype.py index d2995d57a..09426b0e8 100644 --- a/freqtrade/enums/signaltype.py +++ b/freqtrade/enums/signaltype.py @@ -7,6 +7,8 @@ class SignalType(Enum): """ BUY = "buy" SELL = "sell" + SHORT = "short" + EXIT_SHORT = "exit_short" class SignalTagType(Enum): diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index 01f7da19d..3a1e466e0 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -1,6 +1,6 @@ """ Binance exchange subclass """ import logging -from typing import Dict +from typing import Dict, Optional import ccxt @@ -92,3 +92,104 @@ class Binance(Exchange): f'Could not place sell order due to {e.__class__.__name__}. Message: {e}') from e except ccxt.BaseError as e: raise OperationalException(e) from e + + def transfer(self, asset: str, amount: float, frm: str, to: str, pair: Optional[str]): + res = self._api.sapi_post_margin_isolated_transfer({ + "asset": asset, + "amount": amount, + "transFrom": frm, + "transTo": to, + "symbol": pair + }) + logger.info(f"Transfer response: {res}") + + def borrow(self, asset: str, amount: float, pair: str): + res = self._api.sapi_post_margin_loan({ + "asset": asset, + "isIsolated": True, + "symbol": pair, + "amount": amount + }) # borrow from binance + logger.info(f"Borrow response: {res}") + + def repay(self, asset: str, amount: float, pair: str): + res = self._api.sapi_post_margin_repay({ + "asset": asset, + "isIsolated": True, + "symbol": pair, + "amount": amount + }) # borrow from binance + logger.info(f"Borrow response: {res}") + + def setup_leveraged_enter( + self, + pair: str, + leverage: float, + amount: float, + quote_currency: Optional[str], + is_short: Optional[bool] + ): + if not quote_currency or not is_short: + raise OperationalException( + "quote_currency and is_short are required arguments to setup_leveraged_enter" + " when trading with leverage on binance" + ) + open_rate = 2 # TODO-mg: get the real open_rate, or real stake_amount + stake_amount = amount * open_rate + if is_short: + borrowed = stake_amount * ((leverage-1)/leverage) + else: + borrowed = amount + + self.transfer( # Transfer to isolated margin + asset=quote_currency, + amount=stake_amount, + frm='SPOT', + to='ISOLATED_MARGIN', + pair=pair + ) + + self.borrow( + asset=quote_currency, + amount=borrowed, + pair=pair + ) # borrow from binance + + def complete_leveraged_exit( + self, + pair: str, + leverage: float, + amount: float, + quote_currency: Optional[str], + is_short: Optional[bool] + ): + + if not quote_currency or not is_short: + raise OperationalException( + "quote_currency and is_short are required arguments to setup_leveraged_enter" + " when trading with leverage on binance" + ) + + open_rate = 2 # TODO-mg: get the real open_rate, or real stake_amount + stake_amount = amount * open_rate + if is_short: + borrowed = stake_amount * ((leverage-1)/leverage) + else: + borrowed = amount + + self.repay( + asset=quote_currency, + amount=borrowed, + pair=pair + ) # repay binance + + self.transfer( # Transfer to isolated margin + asset=quote_currency, + amount=stake_amount, + frm='ISOLATED_MARGIN', + to='SPOT', + pair=pair + ) + + def apply_leverage_to_stake_amount(self, stake_amount: float, leverage: float): + return stake_amount / leverage diff --git a/freqtrade/exchange/bittrex.py b/freqtrade/exchange/bittrex.py index 69e2f2b8d..e4d344d27 100644 --- a/freqtrade/exchange/bittrex.py +++ b/freqtrade/exchange/bittrex.py @@ -1,8 +1,9 @@ """ Bittrex exchange subclass """ import logging -from typing import Dict +from typing import Dict, Optional from freqtrade.exchange import Exchange +from freqtrade.exceptions import OperationalException logger = logging.getLogger(__name__) @@ -23,3 +24,23 @@ class Bittrex(Exchange): }, "l2_limit_range": [1, 25, 500], } + + def setup_leveraged_enter( + self, + pair: str, + leverage: float, + amount: float, + quote_currency: Optional[str], + is_short: Optional[bool] + ): + raise OperationalException("Bittrex does not support leveraged trading") + + def complete_leveraged_exit( + self, + pair: str, + leverage: float, + amount: float, + quote_currency: Optional[str], + is_short: Optional[bool] + ): + raise OperationalException("Bittrex does not support leveraged trading") diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 7979eac86..082509cd8 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -186,6 +186,7 @@ class Exchange: 'secret': exchange_config.get('secret'), 'password': exchange_config.get('password'), 'uid': exchange_config.get('uid', ''), + 'options': exchange_config.get('options', {}) } if ccxt_kwargs: logger.info('Applying additional ccxt config: %s', ccxt_kwargs) @@ -526,8 +527,9 @@ class Exchange: else: return 1 / pow(10, precision) - def get_min_pair_stake_amount(self, pair: str, price: float, - stoploss: float) -> Optional[float]: + def get_min_pair_stake_amount(self, pair: str, price: float, stoploss: float, + leverage: Optional[float] = 1.0) -> Optional[float]: + # TODO-mg: Using leverage makes the min stake amount lower (on binance at least) try: market = self.markets[pair] except KeyError: @@ -561,7 +563,20 @@ class Exchange: # The value returned should satisfy both limits: for amount (base currency) and # for cost (quote, stake currency), so max() is used here. # See also #2575 at github. - return max(min_stake_amounts) * amount_reserve_percent + return self.apply_leverage_to_stake_amount( + max(min_stake_amounts) * amount_reserve_percent, + leverage or 1.0 + ) + + def apply_leverage_to_stake_amount(self, stake_amount: float, leverage: float): + """ + #* Should be implemented by child classes if leverage affects the stake_amount + Takes the minimum stake amount for a pair with no leverage and returns the minimum + stake amount when leverage is considered + :param stake_amount: The stake amount for a pair before leverage is considered + :param leverage: The amount of leverage being used on the current trade + """ + return stake_amount # Dry-run methods @@ -688,6 +703,15 @@ class Exchange: raise InvalidOrderException( f'Tried to get an invalid dry-run-order (id: {order_id}). Message: {e}') from e + def get_max_leverage(self, pair: str, stake_amount: float, price: float) -> float: + """ + Gets the maximum leverage available on this pair that is below the config leverage + but higher than the config min_leverage + """ + + raise OperationalException(f"Leverage is not available on {self.name} using freqtrade") + return 1.0 + # Order handling def create_order(self, pair: str, ordertype: str, side: str, amount: float, @@ -711,6 +735,7 @@ class Exchange: order = self._api.create_order(pair, ordertype, side, amount, rate_for_order, params) self._log_exchange_response('create_order', order) + return order except ccxt.InsufficientFunds as e: @@ -731,6 +756,26 @@ class Exchange: except ccxt.BaseError as e: raise OperationalException(e) from e + def setup_leveraged_enter( + self, + pair: str, + leverage: float, + amount: float, + quote_currency: Optional[str], + is_short: Optional[bool] + ): + raise OperationalException(f"Leverage is not available on {self.name} using freqtrade") + + def complete_leveraged_exit( + self, + pair: str, + leverage: float, + amount: float, + quote_currency: Optional[str], + is_short: Optional[bool] + ): + raise OperationalException(f"Leverage is not available on {self.name} using freqtrade") + def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool: """ Verify stop_loss against stoploss-order value (limit or price) @@ -1494,6 +1539,9 @@ class Exchange: self._async_get_trade_history(pair=pair, since=since, until=until, from_id=from_id)) + def transfer(self, asset: str, amount: float, frm: str, to: str, pair: Optional[str]): + self._api.transfer(asset, amount, frm, to) + def is_exchange_known_ccxt(exchange_name: str, ccxt_module: CcxtModuleType = None) -> bool: return exchange_name in ccxt_exchanges(ccxt_module) diff --git a/freqtrade/exchange/kraken.py b/freqtrade/exchange/kraken.py index 833ea712d..f247ca144 100644 --- a/freqtrade/exchange/kraken.py +++ b/freqtrade/exchange/kraken.py @@ -1,6 +1,6 @@ """ Kraken exchange subclass """ import logging -from typing import Any, Dict +from typing import Any, Dict, Optional import ccxt @@ -127,3 +127,23 @@ class Kraken(Exchange): f'Could not place sell order due to {e.__class__.__name__}. Message: {e}') from e except ccxt.BaseError as e: raise OperationalException(e) from e + + def setup_leveraged_enter( + self, + pair: str, + leverage: float, + amount: float, + quote_currency: Optional[str], + is_short: Optional[bool] + ): + return + + def complete_leveraged_exit( + self, + pair: str, + leverage: float, + amount: float, + quote_currency: Optional[str], + is_short: Optional[bool] + ): + return diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index ec02a0be8..a5d2f381c 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -412,6 +412,8 @@ class FreqtradeBot(LoggingMixin): def create_trade(self, pair: str) -> bool: """ + # TODO-mg: Just make this function work for shorting and leverage, my todo notes in + # TODO-mg: it aren't very clear Check the implemented trading strategy for enter signals. If the pair triggers the enter signal a new trade record gets created @@ -440,6 +442,8 @@ class FreqtradeBot(LoggingMixin): logger.debug(f"Can't open a new trade for {pair}: max number of trades is reached.") return False + long_lev = 3 if True else 1.0 # Replace with self.strategy.get_leverage + short_lev = 3 if True else 1.0 # Replace with self.strategy.get_leverage # running get_signal on historical data fetched (buy, sell, buy_tag) = self.strategy.get_signal( pair, @@ -448,49 +452,45 @@ class FreqtradeBot(LoggingMixin): ) (short, exit_short) = (False, False) # = self.strategy.get_signal(pair, self.strategy.timeframe, analyzed_df) - # #TODO: Update strategy class to get these values - - open_buy_signal = buy and not sell - open_short_signal = short and not exit_short + # TODO-mg: Update strategy class to get these values # TODO: For a market making strategy, this condition should be removed + open_buy_signal = buy and not sell + open_short_signal = short and not exit_short and not open_buy_signal + if (open_buy_signal or open_short_signal): stake_amount = self.wallets.get_trade_stake_amount(pair, self.edge) if not stake_amount: logger.debug(f"Stake amount is 0, ignoring possible trade for {pair}.") return False - # leverage = 1.0 # TODO: get leverage from somewhere logger.info( f"{'Buy' if open_buy_signal else 'Short'} signal found: " f"about create a new trade for {pair} with stake_amount: " f"{stake_amount} ..." ) - - # TODO: All later code in this function if open_buy_signal: - bid_check_dom = self.config.get('bid_strategy', {}).get('check_depth_of_market', {}) - 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) - else: - return False + strat = 'bid_strategy' + side = 'buy' + # execute_trade = self.execute_enter + lev = long_lev else: - # TODO-mg: Will check_depth_of_market_buy work for shorts? - short_check_dom = self.config.get( - 'short_strategy', {}).get('check_depth_of_market', {}) - if ((short_check_dom.get('enabled', False)) and - (short_check_dom.get('shorts_to_ask_delta', 0) > 0)): - if self._check_depth_of_market_short(pair, short_check_dom): - return self.execute_short(pair, stake_amount) - else: - return False + strat = 'short_strategy' + side = 'sell' + # execute_trade = self.execute_short + lev = short_lev + bid_check_dom = self.config.get(strat, {}).get('check_depth_of_market', {}) + if ((bid_check_dom.get('enabled', False)) and + (bid_check_dom.get('bids_to_ask_delta', 0) > 0)): + if self._check_depth_of_market(pair, bid_check_dom, side): + return self.execute_enter(pair, stake_amount, leverage=lev, buy_tag=buy_tag) + else: + return False - return self.execute_buy(pair, stake_amount, buy_tag=buy_tag) + return self.execute_enter(pair, stake_amount, leverage=lev, buy_tag=buy_tag) else: return False - def _check_depth_of_market_buy(self, pair: str, conf: Dict) -> bool: + def _check_depth_of_market(self, pair: str, conf: Dict, side: str) -> bool: """ Checks depth of market before executing a buy """ @@ -500,9 +500,17 @@ class FreqtradeBot(LoggingMixin): order_book_data_frame = order_book_to_dataframe(order_book['bids'], order_book['asks']) order_book_bids = order_book_data_frame['b_size'].sum() order_book_asks = order_book_data_frame['a_size'].sum() - bids_ask_delta = order_book_bids / order_book_asks + + enter_side = order_book_bids if side == "buy" else order_book_asks + exit_side = order_book_asks if side == "buy" else order_book_bids + bids_ask_delta = enter_side / exit_side + + bids = f"Bids: {order_book_bids}" + asks = f"Asks: {order_book_asks}" + delta = f"Delta: {bids_ask_delta}" + logger.info( - f"Bids: {order_book_bids}, Asks: {order_book_asks}, Delta: {bids_ask_delta}, " + f"{bids}, {asks}, {delta}, Side: {side}" f"Bid Price: {order_book['bids'][0][0]}, Ask Price: {order_book['asks'][0][0]}, " f"Immediate Bid Quantity: {order_book['bids'][0][1]}, " f"Immediate Ask Quantity: {order_book['asks'][0][1]}." @@ -514,68 +522,80 @@ class FreqtradeBot(LoggingMixin): logger.info(f"Bids to asks delta for {pair} does not satisfy condition.") return False - def _check_depth_of_market_short(self, pair: str, conf: Dict) -> bool: - # TODO-mg: implement, use _check_depth_of_market_buy instead if possible - 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_enter( + self, + pair: str, + stake_amount: float, + price: Optional[float] = None, + forcebuy: bool = False, + leverage: Optional[float] = 1.0, + is_short: 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 :param stake_amount: amount of stake-currency for the pair + :param leverage: amount of leverage applied to this trade :return: True if a buy order is created, false if it fails. """ - time_in_force = self.strategy.order_time_in_force['buy'] + time_in_force = self.strategy.order_time_in_force['buy'] # TODO-mg Change to enter + trade_type = 'short' if is_short else 'buy' if price: - buy_limit_requested = price + enter_limit_requested = price else: # Calculate price - buy_limit_requested = self.exchange.get_rate(pair, refresh=True, side="buy") + enter_limit_requested = self.exchange.get_rate(pair, refresh=True, side=trade_type) - if not buy_limit_requested: + if not enter_limit_requested: raise PricingError('Could not determine buy price.') - min_stake_amount = self.exchange.get_min_pair_stake_amount(pair, buy_limit_requested, - self.strategy.stoploss) + min_stake_amount = self.exchange.get_min_pair_stake_amount( + pair=pair, + price=enter_limit_requested, + stoploss=self.strategy.stoploss, + leverage=leverage + ) if not self.edge: max_stake_amount = self.wallets.get_available_stake_amount() stake_amount = strategy_safe_wrapper(self.strategy.custom_stake_amount, default_retval=stake_amount)( pair=pair, current_time=datetime.now(timezone.utc), - current_rate=buy_limit_requested, proposed_stake=stake_amount, + current_rate=enter_limit_requested, proposed_stake=stake_amount, min_stake=min_stake_amount, max_stake=max_stake_amount) stake_amount = self.wallets._validate_stake_amount(pair, stake_amount, min_stake_amount) if not stake_amount: return False - logger.info(f"Buy signal found: about create a new trade for {pair} with stake_amount: " + log_type = f"{trade_type.capitalize()} signal found" + logger.info(f"{log_type}: about create a new trade for {pair} with stake_amount: " f"{stake_amount} ...") - amount = stake_amount / buy_limit_requested - order_type = self.strategy.order_types['buy'] + amount = stake_amount / enter_limit_requested + order_type = self.strategy.order_types[trade_type] if forcebuy: # Forcebuy can define a different ordertype + # TODO-mg get a forceshort order_type = self.strategy.order_types.get('forcebuy', order_type) if not strategy_safe_wrapper(self.strategy.confirm_trade_entry, default_retval=True)( - pair=pair, order_type=order_type, amount=amount, rate=buy_limit_requested, + pair=pair, order_type=order_type, amount=amount, rate=enter_limit_requested, time_in_force=time_in_force, current_time=datetime.now(timezone.utc)): logger.info(f"User requested abortion of buying {pair}") return False amount = self.exchange.amount_to_precision(pair, amount) order = self.exchange.create_order(pair=pair, ordertype=order_type, side="buy", - amount=amount, rate=buy_limit_requested, + amount=amount, rate=enter_limit_requested, time_in_force=time_in_force) order_obj = Order.parse_from_ccxt_object(order, pair, 'buy') order_id = order['id'] order_status = order.get('status', None) # we assume the order is executed at the price requested - buy_limit_filled_price = buy_limit_requested + buy_limit_filled_price = enter_limit_requested amount_requested = amount if order_status == 'expired' or order_status == 'rejected': @@ -617,7 +637,7 @@ class FreqtradeBot(LoggingMixin): fee_open=fee, fee_close=fee, open_rate=buy_limit_filled_price, - open_rate_requested=buy_limit_requested, + open_rate_requested=enter_limit_requested, open_date=datetime.utcnow(), exchange=self.exchange.id, open_order_id=order_id, @@ -637,21 +657,17 @@ class FreqtradeBot(LoggingMixin): # Updating wallets self.wallets.update() - self._notify_buy(trade, order_type) + self._notify_open(trade, order_type) return True - def execute_short(self, pair: str, stake_amount: float, price: Optional[float] = None, - forcesell: bool = False) -> bool: - return False # TODO-mg: implement - - def _notify_buy(self, trade: Trade, order_type: str) -> None: + def _notify_open(self, trade: Trade, order_type: str) -> None: """ - Sends rpc notification when a buy occurred. + Sends rpc notification when a buy/short occurred. """ msg = { 'trade_id': trade.id, - 'type': RPCMessageType.BUY, + 'type': RPCMessageType.SHORT if trade.is_short else RPCMessageType.BUY, 'buy_tag': trade.buy_tag, 'exchange': self.exchange.name.capitalize(), 'pair': trade.pair, @@ -668,15 +684,15 @@ class FreqtradeBot(LoggingMixin): # Send the message self.rpc.send_msg(msg) - def _notify_buy_cancel(self, trade: Trade, order_type: str, reason: str) -> None: + def _notify_open_cancel(self, trade: Trade, order_type: str, reason: str) -> None: """ - Sends rpc notification when a buy cancel occurred. + Sends rpc notification when a buy/short cancel occurred. """ current_rate = self.exchange.get_rate(trade.pair, refresh=False, side="buy") - + msg_type = RPCMessageType.SHORT_CANCEL if trade.is_short else RPCMessageType.BUY_CANCEL msg = { 'trade_id': trade.id, - 'type': RPCMessageType.BUY_CANCEL, + 'type': msg_type, 'buy_tag': trade.buy_tag, 'exchange': self.exchange.name.capitalize(), 'pair': trade.pair, @@ -694,10 +710,11 @@ class FreqtradeBot(LoggingMixin): # Send the message self.rpc.send_msg(msg) - def _notify_buy_fill(self, trade: Trade) -> None: + def _notify_open_fill(self, trade: Trade) -> None: + msg_type = RPCMessageType.SHORT_FILL if trade.is_short else RPCMessageType.BUY_FILL msg = { 'trade_id': trade.id, - 'type': RPCMessageType.BUY_FILL, + 'type': msg_type, 'buy_tag': trade.buy_tag, 'exchange': self.exchange.name.capitalize(), 'pair': trade.pair, @@ -716,7 +733,7 @@ class FreqtradeBot(LoggingMixin): def exit_positions(self, trades: List[Any]) -> int: """ - Tries to execute sell orders for open trades (positions) + Tries to execute sell/exit_short orders for open trades (positions) """ trades_closed = 0 for trade in trades: @@ -732,7 +749,7 @@ class FreqtradeBot(LoggingMixin): trades_closed += 1 except DependencyException as exception: - logger.warning('Unable to sell trade %s: %s', trade.pair, exception) + logger.warning('Unable to exit trade %s: %s', trade.pair, exception) # Updating wallets if any trade occurred if trades_closed: @@ -742,33 +759,42 @@ class FreqtradeBot(LoggingMixin): def handle_trade(self, trade: Trade) -> bool: """ - Sells the current pair if the threshold is reached and updates the trade record. - :return: True if trade has been sold, False otherwise + Sells/exits_short the current pair if the threshold is reached and updates the trade record. + :return: True if trade has been sold/exited_short, False otherwise """ if not trade.is_open: raise DependencyException(f'Attempt to handle closed trade: {trade}') logger.debug('Handling %s ...', trade) - (buy, sell) = (False, False) + (enter_trade, exit_trade) = (False, False) - if (self.config.get('use_sell_signal', True) or - self.config.get('ignore_roi_if_buy_signal', False)): + if trade.is_short: + # enter_signal = "short" + exit_signal = "exit_short" + signal_config = (self.config.get('use_exit_short_signal', True) + or self.config.get('ignore_roi_if_short_signal', False)) + else: + # enter_signal = "buy" + exit_signal = "sell" + signal_config = (self.config.get('use_sell_signal', True) + or self.config.get('ignore_roi_if_buy_signal', False)) + + if (signal_config): analyzed_df, _ = self.dataprovider.get_analyzed_dataframe(trade.pair, self.strategy.timeframe) + (enter_trade, exit_trade, _) = self.strategy.get_signal( + trade.pair, self.strategy.timeframe, analyzed_df) - (buy, sell, _) = self.strategy.get_signal( - trade.pair, - self.strategy.timeframe, - analyzed_df - ) - - logger.debug('checking sell') sell_rate = self.exchange.get_rate(trade.pair, refresh=True, side="sell") - if self._check_and_execute_sell(trade, sell_rate, buy, sell): - return True - logger.debug('Found no sell signal for %s.', trade) + # TODO-mg: This will not works for shorts, + # TODO-mg: update exchange.get_exit_rate and _check_and_execute_sell + if self._check_and_execute_sell(trade, sell_rate, enter_trade, exit_trade): + return True + # TODO-mg: end of non-working section + + logger.debug(f'Found no {exit_signal} signal for %s.', trade) return False def create_stoploss_order(self, trade: Trade, stop_price: float) -> bool: @@ -795,9 +821,14 @@ class FreqtradeBot(LoggingMixin): except InvalidOrderException as e: 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( - sell_type=SellType.EMERGENCY_SELL)) + if trade.is_short: + logger.warning('Exiting(Buying) the trade forcefully') + # self.execute_exit_short(trade, trade.stop_loss, sell_reason=SellCheckTuple( + # sell_type=SellType.EMERGENCY_SELL)) + else: + logger.warning('Selling the trade forcefully') + self.execute_sell(trade, trade.stop_loss, sell_reason=SellCheckTuple( + sell_type=SellType.EMERGENCY_SELL)) except ExchangeError: trade.stoploss_order_id = None @@ -809,6 +840,8 @@ class FreqtradeBot(LoggingMixin): Check if trade is fulfilled in which case the stoploss on exchange should be added immediately if stoploss on exchange is enabled. + # TODO-mg: liquidation price will always be on exchange, even though + # TODO-mg: stoploss_on_exchange might not be enabled """ logger.debug('Handling stoploss on exchange %s ...', trade) @@ -827,13 +860,15 @@ class FreqtradeBot(LoggingMixin): # We check if stoploss order is fulfilled if stoploss_order and stoploss_order['status'] in ('closed', 'triggered'): + # TODO-mg: Update to exit/close reason (doesn't affect code functionality) trade.sell_reason = SellType.STOPLOSS_ON_EXCHANGE.value self.update_trade_state(trade, trade.stoploss_order_id, stoploss_order, stoploss_order=True) # Lock pair for one candle to prevent immediate rebuys + # TODO: Disble this lock for market making strategies self.strategy.lock_pair(trade.pair, datetime.now(timezone.utc), reason='Auto lock') - self._notify_sell(trade, "stoploss") + self._notify_close(trade, "stoploss") return True if trade.open_order_id or not trade.is_open: @@ -842,10 +877,13 @@ class FreqtradeBot(LoggingMixin): # The trade can be closed already (sell-order fill confirmation came in this iteration) return False - # If buy order is fulfilled but there is no stoploss, we add a stoploss on exchange + # If enter order is fulfilled but there is no stoploss, we add a stoploss on exchange if not stoploss_order: stoploss = self.edge.stoploss(pair=trade.pair) if self.edge else self.strategy.stoploss - stop_price = trade.open_rate * (1 + stoploss) + if trade.is_short: + stop_price = trade.open_rate * (1 - stoploss) + else: + stop_price = trade.open_rate * (1 + stoploss) if self.create_stoploss_order(trade=trade, stop_price=stop_price): trade.stoploss_last_update = datetime.utcnow() @@ -906,6 +944,7 @@ class FreqtradeBot(LoggingMixin): buy: bool, sell: bool) -> bool: """ Check and execute sell + # TODO-mg: Update this for shorts """ should_sell = self.strategy.should_sell( trade, sell_rate, datetime.now(timezone.utc), buy, sell, @@ -950,14 +989,15 @@ class FreqtradeBot(LoggingMixin): fully_cancelled = self.update_trade_state(trade, trade.open_order_id, order) - if (order['side'] == 'buy' and (order['status'] == 'open' or fully_cancelled) and ( + if (order['side'] == trade.enter_side and ( + order['status'] == 'open' or fully_cancelled) and ( fully_cancelled - or self._check_timed_out('buy', order) + or self._check_timed_out(trade.enter_side, order) or strategy_safe_wrapper(self.strategy.check_buy_timeout, default_retval=False)(pair=trade.pair, trade=trade, order=order))): - self.handle_cancel_buy(trade, order, constants.CANCEL_REASON['TIMEOUT']) + self.handle_cancel_enter(trade, order, constants.CANCEL_REASON['TIMEOUT']) elif (order['side'] == 'sell' and (order['status'] == 'open' or fully_cancelled) and ( fully_cancelled @@ -982,13 +1022,13 @@ class FreqtradeBot(LoggingMixin): continue if order['side'] == 'buy': - self.handle_cancel_buy(trade, order, constants.CANCEL_REASON['ALL_CANCELLED']) + self.handle_cancel_enter(trade, order, constants.CANCEL_REASON['ALL_CANCELLED']) elif order['side'] == 'sell': self.handle_cancel_sell(trade, order, constants.CANCEL_REASON['ALL_CANCELLED']) Trade.commit() - def handle_cancel_buy(self, trade: Trade, order: Dict, reason: str) -> bool: + def handle_cancel_enter(self, trade: Trade, order: Dict, reason: str) -> bool: """ Buy cancel - cancel order :return: True if order was fully cancelled @@ -1005,7 +1045,7 @@ class FreqtradeBot(LoggingMixin): if filled_val > 0 and filled_stake < minstake: logger.warning( f"Order {trade.open_order_id} for {trade.pair} not cancelled, " - f"as the filled amount of {filled_val} would result in an unsellable trade.") + f"as the filled amount of {filled_val} would result in an uncloseable trade.") return False corder = self.exchange.cancel_order_with_result(trade.open_order_id, trade.pair, trade.amount) @@ -1020,12 +1060,19 @@ class FreqtradeBot(LoggingMixin): corder = order reason = constants.CANCEL_REASON['CANCELLED_ON_EXCHANGE'] + # TODO-mg: get this side variable working, fails in tests + # side = trade.enter_side[0].upper() + trade.enter_side[1:].lower() logger.info('Buy order %s for %s.', reason, trade) + # logger.info(f'{side} order %s for %s.', reason, trade) # Using filled to determine the filled amount filled_amount = safe_value_fallback2(corder, order, 'filled', 'filled') if isclose(filled_amount, 0.0, abs_tol=constants.MATH_CLOSE_PREC): - logger.info('Buy order fully cancelled. Removing %s from database.', trade) + logger.info( + 'Buy order fully cancelled. Removing %s from database.', + # f'{side} order fully cancelled. Removing %s from database.', + trade + ) # if trade is not partially completed, just delete the trade trade.delete() was_trade_fully_canceled = True @@ -1036,17 +1083,21 @@ class FreqtradeBot(LoggingMixin): # cancel_order may not contain the full order dict, so we need to fallback # to the order dict aquired before cancelling. # we need to fall back to the values from order if corder does not contain these keys. + # TODO-mg: Update trade.leverage if the full buy order was not filled trade.amount = filled_amount trade.stake_amount = trade.amount * trade.open_rate self.update_trade_state(trade, trade.open_order_id, corder) trade.open_order_id = None + # TODO-mg change to buy or short order logger.info('Partial buy order timeout for %s.', trade) reason += f", {constants.CANCEL_REASON['PARTIALLY_FILLED']}" self.wallets.update() - self._notify_buy_cancel(trade, order_type=self.strategy.order_types['buy'], - reason=reason) + # TODO-mg change to buy or short order + # TODO-mg: Or should it be self.strategy.order_types['short'] or strategy.order_types['buy'] + self._notify_open_cancel(trade, order_type=self.strategy.order_types['buy'], + reason=reason) return was_trade_fully_canceled def handle_cancel_sell(self, trade: Trade, order: Dict, reason: str) -> str: @@ -1083,7 +1134,7 @@ class FreqtradeBot(LoggingMixin): reason = constants.CANCEL_REASON['PARTIALLY_FILLED_KEEP_OPEN'] self.wallets.update() - self._notify_sell_cancel( + self._notify_close_cancel( trade, order_type=self.strategy.order_types['sell'], reason=reason @@ -1123,6 +1174,7 @@ class FreqtradeBot(LoggingMixin): :param sell_reason: Reason the sell was triggered :return: True if it succeeds (supported) False (not supported) """ + # TODO-mg: account for leverage and make this work for shorts sell_type = 'sell' if sell_reason.sell_type in (SellType.STOP_LOSS, SellType.TRAILING_STOP_LOSS): sell_type = 'stoploss' @@ -1190,11 +1242,11 @@ class FreqtradeBot(LoggingMixin): self.strategy.lock_pair(trade.pair, datetime.now(timezone.utc), reason='Auto lock') - self._notify_sell(trade, order_type) + self._notify_close(trade, order_type) return True - def _notify_sell(self, trade: Trade, order_type: str, fill: bool = False) -> None: + def _notify_close(self, trade: Trade, order_type: str, fill: bool = False) -> None: """ Sends rpc notification when a sell occurred. """ @@ -1236,7 +1288,7 @@ class FreqtradeBot(LoggingMixin): # Send the message self.rpc.send_msg(msg) - def _notify_sell_cancel(self, trade: Trade, order_type: str, reason: str) -> None: + def _notify_close_cancel(self, trade: Trade, order_type: str, reason: str) -> None: """ Sends rpc notification when a sell cancel occurred. """ @@ -1331,13 +1383,13 @@ class FreqtradeBot(LoggingMixin): # Updating wallets when order is closed if not trade.is_open: if not stoploss_order and not trade.open_order_id: - self._notify_sell(trade, '', True) + self._notify_close(trade, '', True) self.protections.stop_per_pair(trade.pair) self.protections.global_stop() self.wallets.update() elif not trade.open_order_id: # Buy fill - self._notify_buy_fill(trade) + self._notify_open_fill(trade) return False @@ -1350,6 +1402,7 @@ class FreqtradeBot(LoggingMixin): self.wallets.update() if fee_abs != 0 and self.wallets.get_free(trade_base_currency) >= amount: # Eat into dust if we own more than base currency + # TODO-mg: won't be in "base"(quote) currency for shorts logger.info(f"Fee amount for {trade} was in base currency - " f"Eating Fee {fee_abs} into dust.") elif fee_abs != 0: @@ -1361,6 +1414,7 @@ class FreqtradeBot(LoggingMixin): def get_real_amount(self, trade: Trade, order: Dict) -> float: """ + TODO-mg: Update this function to account for shorts Detect and update trade fee. Calls trade.update_fee() upon correct detection. Returns modified amount if the fee was taken from the destination currency. @@ -1393,6 +1447,7 @@ class FreqtradeBot(LoggingMixin): def fee_detection_from_trades(self, trade: Trade, order: Dict, order_amount: float) -> float: """ + TODO-mg: Update this function to account for shorts fee-detection fallback to Trades. Parses result of fetch_my_trades to get correct fee. """ trades = self.exchange.get_trades_for_order(self.exchange.get_order_id_conditional(order), diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 902975fde..8bcc1044e 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -36,6 +36,7 @@ class RPCException(Exception): raise RPCException('*Status:* `no active trade`') """ + # TODO-mg: Add new configuration options introduced with leveraged/short trading def __init__(self, message: str) -> None: super().__init__(self) @@ -545,7 +546,7 @@ class RPC: order = self._freqtrade.exchange.fetch_order(trade.open_order_id, trade.pair) if order['side'] == 'buy': - fully_canceled = self._freqtrade.handle_cancel_buy( + fully_canceled = self._freqtrade.handle_cancel_enter( trade, order, CANCEL_REASON['FORCE_SELL']) if order['side'] == 'sell': @@ -613,7 +614,7 @@ class RPC: stakeamount = self._freqtrade.wallets.get_trade_stake_amount(pair) # execute buy - if self._freqtrade.execute_buy(pair, stakeamount, price, forcebuy=True): + if self._freqtrade.execute_enter(pair, stakeamount, price, forcebuy=True): Trade.commit() trade = Trade.get_trades([Trade.is_open.is_(True), Trade.pair == pair]).first() return trade diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 1cac9736a..db049241a 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -185,7 +185,7 @@ def test_check_available_stake_amount(default_conf, ticker, mocker, fee, limit_b limit_buy_order_open['id'] = str(i) result = freqtrade.wallets.get_trade_stake_amount('ETH/BTC') assert pytest.approx(result) == expected[i] - freqtrade.execute_buy('ETH/BTC', result) + freqtrade.execute_enter('ETH/BTC', result) else: with pytest.raises(DependencyException): freqtrade.wallets.get_trade_stake_amount('ETH/BTC') @@ -584,8 +584,8 @@ def test_create_trades_preopen(default_conf, ticker, fee, mocker, limit_buy_orde patch_get_signal(freqtrade) # Create 2 existing trades - freqtrade.execute_buy('ETH/BTC', default_conf['stake_amount']) - freqtrade.execute_buy('NEO/BTC', default_conf['stake_amount']) + freqtrade.execute_enter('ETH/BTC', default_conf['stake_amount']) + freqtrade.execute_enter('NEO/BTC', default_conf['stake_amount']) assert len(Trade.get_open_trades()) == 2 # Change order_id for new orders @@ -776,7 +776,7 @@ def test_process_informative_pairs_added(default_conf, ticker, mocker) -> None: assert ("ETH/BTC", default_conf["timeframe"]) in refresh_mock.call_args[0][0] -def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order_open) -> None: +def test_execute_enter(mocker, default_conf, fee, limit_buy_order, limit_buy_order_open) -> None: patch_RPCManager(mocker) patch_exchange(mocker) freqtrade = FreqtradeBot(default_conf) @@ -784,7 +784,9 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order stake_amount = 2 bid = 0.11 buy_rate_mock = MagicMock(return_value=bid) - buy_mm = MagicMock(return_value=limit_buy_order_open) + buy_mm = MagicMock( + return_value=limit_buy_order_open + ) mocker.patch.multiple( 'freqtrade.exchange.Exchange', get_rate=buy_rate_mock, @@ -799,7 +801,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order ) pair = 'ETH/BTC' - assert not freqtrade.execute_buy(pair, stake_amount) + assert not freqtrade.execute_enter(pair, stake_amount) assert buy_rate_mock.call_count == 1 assert buy_mm.call_count == 0 assert freqtrade.strategy.confirm_trade_entry.call_count == 1 @@ -807,7 +809,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order limit_buy_order_open['id'] = '22' freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=True) - assert freqtrade.execute_buy(pair, stake_amount) + assert freqtrade.execute_enter(pair, stake_amount) assert buy_rate_mock.call_count == 1 assert buy_mm.call_count == 1 call_args = buy_mm.call_args_list[0][1] @@ -826,7 +828,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order # Test calling with price limit_buy_order_open['id'] = '33' fix_price = 0.06 - assert freqtrade.execute_buy(pair, stake_amount, fix_price) + assert freqtrade.execute_enter(pair, stake_amount, fix_price) # Make sure get_rate wasn't called again assert buy_rate_mock.call_count == 0 @@ -844,7 +846,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order mocker.patch('freqtrade.exchange.Exchange.create_order', MagicMock(return_value=limit_buy_order)) - assert freqtrade.execute_buy(pair, stake_amount) + assert freqtrade.execute_enter(pair, stake_amount) trade = Trade.query.all()[2] assert trade assert trade.open_order_id is None @@ -861,7 +863,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order limit_buy_order['id'] = '555' mocker.patch('freqtrade.exchange.Exchange.create_order', MagicMock(return_value=limit_buy_order)) - assert freqtrade.execute_buy(pair, stake_amount) + assert freqtrade.execute_enter(pair, stake_amount) trade = Trade.query.all()[3] assert trade assert trade.open_order_id == '555' @@ -873,7 +875,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order limit_buy_order['id'] = '556' freqtrade.strategy.custom_stake_amount = lambda **kwargs: 150.0 - assert freqtrade.execute_buy(pair, stake_amount) + assert freqtrade.execute_enter(pair, stake_amount) trade = Trade.query.all()[4] assert trade assert trade.stake_amount == 150 @@ -881,7 +883,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order # Exception case limit_buy_order['id'] = '557' freqtrade.strategy.custom_stake_amount = lambda **kwargs: 20 / 0 - assert freqtrade.execute_buy(pair, stake_amount) + assert freqtrade.execute_enter(pair, stake_amount) trade = Trade.query.all()[5] assert trade assert trade.stake_amount == 2.0 @@ -896,16 +898,16 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order limit_buy_order['id'] = '66' mocker.patch('freqtrade.exchange.Exchange.create_order', MagicMock(return_value=limit_buy_order)) - assert not freqtrade.execute_buy(pair, stake_amount) + assert not freqtrade.execute_enter(pair, stake_amount) # Fail to get price... mocker.patch('freqtrade.exchange.Exchange.get_rate', MagicMock(return_value=0.0)) with pytest.raises(PricingError, match="Could not determine buy price."): - freqtrade.execute_buy(pair, stake_amount) + freqtrade.execute_enter(pair, stake_amount) -def test_execute_buy_confirm_error(mocker, default_conf, fee, limit_buy_order) -> None: +def test_execute_enter_confirm_error(mocker, default_conf, fee, limit_buy_order) -> None: freqtrade = get_patched_freqtradebot(mocker, default_conf) mocker.patch.multiple( 'freqtrade.exchange.Exchange', @@ -923,18 +925,18 @@ def test_execute_buy_confirm_error(mocker, default_conf, fee, limit_buy_order) - pair = 'ETH/BTC' freqtrade.strategy.confirm_trade_entry = MagicMock(side_effect=ValueError) - assert freqtrade.execute_buy(pair, stake_amount) + assert freqtrade.execute_enter(pair, stake_amount) limit_buy_order['id'] = '222' freqtrade.strategy.confirm_trade_entry = MagicMock(side_effect=Exception) - assert freqtrade.execute_buy(pair, stake_amount) + assert freqtrade.execute_enter(pair, stake_amount) limit_buy_order['id'] = '2223' freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=True) - assert freqtrade.execute_buy(pair, stake_amount) + assert freqtrade.execute_enter(pair, stake_amount) freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=False) - assert not freqtrade.execute_buy(pair, stake_amount) + assert not freqtrade.execute_enter(pair, stake_amount) def test_add_stoploss_on_exchange(mocker, default_conf, limit_buy_order) -> None: @@ -1686,7 +1688,7 @@ def test_exit_positions_exception(mocker, default_conf, limit_buy_order, caplog) ) n = freqtrade.exit_positions(trades) assert n == 0 - assert log_has('Unable to sell trade ETH/BTC: ', caplog) + assert log_has('Unable to exit trade ETH/BTC: ', caplog) def test_update_trade_state(mocker, default_conf, limit_buy_order, caplog) -> None: @@ -2419,7 +2421,7 @@ def test_check_handle_timedout_exception(default_conf, ticker, open_trade, mocke mocker.patch.multiple( 'freqtrade.freqtradebot.FreqtradeBot', - handle_cancel_buy=MagicMock(), + handle_cancel_enter=MagicMock(), handle_cancel_sell=MagicMock(), ) mocker.patch.multiple( @@ -2441,7 +2443,7 @@ def test_check_handle_timedout_exception(default_conf, ticker, open_trade, mocke caplog) -def test_handle_cancel_buy(mocker, caplog, default_conf, limit_buy_order) -> None: +def test_handle_cancel_enter(mocker, caplog, default_conf, limit_buy_order) -> None: patch_RPCManager(mocker) patch_exchange(mocker) cancel_buy_order = deepcopy(limit_buy_order) @@ -2452,54 +2454,56 @@ def test_handle_cancel_buy(mocker, caplog, default_conf, limit_buy_order) -> Non mocker.patch('freqtrade.exchange.Exchange.cancel_order_with_result', cancel_order_mock) freqtrade = FreqtradeBot(default_conf) - freqtrade._notify_buy_cancel = MagicMock() + freqtrade._notify_open_cancel = MagicMock() trade = MagicMock() trade.pair = 'LTC/USDT' trade.open_rate = 200 + trade.is_short = False + trade.enter_side = "buy" limit_buy_order['filled'] = 0.0 limit_buy_order['status'] = 'open' reason = CANCEL_REASON['TIMEOUT'] - assert freqtrade.handle_cancel_buy(trade, limit_buy_order, reason) + assert freqtrade.handle_cancel_enter(trade, limit_buy_order, reason) assert cancel_order_mock.call_count == 1 cancel_order_mock.reset_mock() caplog.clear() limit_buy_order['filled'] = 0.01 - assert not freqtrade.handle_cancel_buy(trade, limit_buy_order, reason) + assert not freqtrade.handle_cancel_enter(trade, limit_buy_order, reason) assert cancel_order_mock.call_count == 0 - assert log_has_re("Order .* for .* not cancelled, as the filled amount.* unsellable.*", caplog) + assert log_has_re("Order .* for .* not cancelled, as the filled amount.* uncloseable.*", caplog) caplog.clear() cancel_order_mock.reset_mock() limit_buy_order['filled'] = 2 - assert not freqtrade.handle_cancel_buy(trade, limit_buy_order, reason) + assert not freqtrade.handle_cancel_enter(trade, limit_buy_order, reason) assert cancel_order_mock.call_count == 1 # Order remained open for some reason (cancel failed) cancel_buy_order['status'] = 'open' cancel_order_mock = MagicMock(return_value=cancel_buy_order) mocker.patch('freqtrade.exchange.Exchange.cancel_order_with_result', cancel_order_mock) - assert not freqtrade.handle_cancel_buy(trade, limit_buy_order, reason) + assert not freqtrade.handle_cancel_enter(trade, limit_buy_order, reason) assert log_has_re(r"Order .* for .* not cancelled.", caplog) @pytest.mark.parametrize("limit_buy_order_canceled_empty", ['binance', 'ftx', 'kraken', 'bittrex'], indirect=['limit_buy_order_canceled_empty']) -def test_handle_cancel_buy_exchanges(mocker, caplog, default_conf, - limit_buy_order_canceled_empty) -> None: +def test_handle_cancel_enter_exchanges(mocker, caplog, default_conf, + limit_buy_order_canceled_empty) -> None: patch_RPCManager(mocker) patch_exchange(mocker) cancel_order_mock = mocker.patch( 'freqtrade.exchange.Exchange.cancel_order_with_result', return_value=limit_buy_order_canceled_empty) - nofiy_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot._notify_buy_cancel') + nofiy_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot._notify_open_cancel') freqtrade = FreqtradeBot(default_conf) reason = CANCEL_REASON['TIMEOUT'] trade = MagicMock() trade.pair = 'LTC/ETH' - assert freqtrade.handle_cancel_buy(trade, limit_buy_order_canceled_empty, reason) + assert freqtrade.handle_cancel_enter(trade, limit_buy_order_canceled_empty, reason) assert cancel_order_mock.call_count == 0 assert log_has_re(r'Buy order fully cancelled. Removing .* from database\.', caplog) assert nofiy_mock.call_count == 1 @@ -2511,8 +2515,8 @@ def test_handle_cancel_buy_exchanges(mocker, caplog, default_conf, 'String Return value', 123 ]) -def test_handle_cancel_buy_corder_empty(mocker, default_conf, limit_buy_order, - cancelorder) -> None: +def test_handle_cancel_enter_corder_empty(mocker, default_conf, limit_buy_order, + cancelorder) -> None: patch_RPCManager(mocker) patch_exchange(mocker) cancel_order_mock = MagicMock(return_value=cancelorder) @@ -2522,7 +2526,7 @@ def test_handle_cancel_buy_corder_empty(mocker, default_conf, limit_buy_order, ) freqtrade = FreqtradeBot(default_conf) - freqtrade._notify_buy_cancel = MagicMock() + freqtrade._notify_open_cancel = MagicMock() trade = MagicMock() trade.pair = 'LTC/USDT' @@ -2530,12 +2534,12 @@ def test_handle_cancel_buy_corder_empty(mocker, default_conf, limit_buy_order, limit_buy_order['filled'] = 0.0 limit_buy_order['status'] = 'open' reason = CANCEL_REASON['TIMEOUT'] - assert freqtrade.handle_cancel_buy(trade, limit_buy_order, reason) + assert freqtrade.handle_cancel_enter(trade, limit_buy_order, reason) assert cancel_order_mock.call_count == 1 cancel_order_mock.reset_mock() limit_buy_order['filled'] = 1.0 - assert not freqtrade.handle_cancel_buy(trade, limit_buy_order, reason) + assert not freqtrade.handle_cancel_enter(trade, limit_buy_order, reason) assert cancel_order_mock.call_count == 1 @@ -4099,7 +4103,7 @@ def test_check_depth_of_market_buy(default_conf, mocker, order_book_l2) -> None: freqtrade = FreqtradeBot(default_conf) conf = default_conf['bid_strategy']['check_depth_of_market'] - assert freqtrade._check_depth_of_market_buy('ETH/BTC', conf) is False + assert freqtrade._check_depth_of_market('ETH/BTC', conf, side="buy") is False def test_order_book_ask_strategy(default_conf, limit_buy_order_open, limit_buy_order, fee, @@ -4216,7 +4220,7 @@ def test_cancel_all_open_orders(mocker, default_conf, fee, limit_buy_order, limi mocker.patch('freqtrade.exchange.Exchange.fetch_order', side_effect=[ ExchangeError(), limit_sell_order, limit_buy_order, limit_sell_order]) - buy_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_cancel_buy') + buy_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_cancel_enter') sell_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_cancel_sell') freqtrade = get_patched_freqtradebot(mocker, default_conf) diff --git a/tests/test_integration.py b/tests/test_integration.py index b12959a03..d89448833 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -70,7 +70,7 @@ def test_may_execute_sell_stoploss_on_exchange_multi(default_conf, ticker, fee, mocker.patch.multiple( 'freqtrade.freqtradebot.FreqtradeBot', create_stoploss_order=MagicMock(return_value=True), - _notify_sell=MagicMock(), + _notify_close=MagicMock(), ) mocker.patch("freqtrade.strategy.interface.IStrategy.should_sell", should_sell_mock) wallets_mock = mocker.patch("freqtrade.wallets.Wallets.update", MagicMock()) @@ -154,7 +154,7 @@ def test_forcebuy_last_unlimited(default_conf, ticker, fee, limit_buy_order, moc mocker.patch.multiple( 'freqtrade.freqtradebot.FreqtradeBot', create_stoploss_order=MagicMock(return_value=True), - _notify_sell=MagicMock(), + _notify_close=MagicMock(), ) should_sell_mock = MagicMock(side_effect=[ SellCheckTuple(sell_type=SellType.NONE), diff --git a/tests/test_wallets.py b/tests/test_wallets.py index 9f58cb71d..f08b3410a 100644 --- a/tests/test_wallets.py +++ b/tests/test_wallets.py @@ -157,13 +157,13 @@ def test_get_trade_stake_amount_unlimited_amount(default_conf, ticker, balance_r assert result == result1 # create one trade, order amount should be 'balance / (max_open_trades - num_open_trades)' - freqtrade.execute_buy('ETH/USDT', result) + freqtrade.execute_enter('ETH/USDT', result) result = freqtrade.wallets.get_trade_stake_amount('LTC/USDT') assert result == result1 # create 2 trades, order amount should be None - freqtrade.execute_buy('LTC/BTC', result) + freqtrade.execute_enter('LTC/BTC', result) result = freqtrade.wallets.get_trade_stake_amount('XRP/USDT') assert result == 0