diff --git a/freqtrade/constants.py b/freqtrade/constants.py index c6b8f0e62..ee104325b 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -39,6 +39,8 @@ DEFAULT_DATAFRAME_COLUMNS = ['date', 'open', 'high', 'low', 'close', 'volume'] # Don't modify sequence of DEFAULT_TRADES_COLUMNS # it has wide consequences for stored trades files DEFAULT_TRADES_COLUMNS = ['timestamp', 'id', 'type', 'side', 'price', 'amount', 'cost'] +TRADING_MODES = ['spot', 'margin', 'futures'] +COLLATERAL_TYPES = ['cross', 'isolated'] LAST_BT_RESULT_FN = '.last_result.json' FTHYPT_FILEVERSION = 'fthypt_fileversion' @@ -146,6 +148,8 @@ CONF_SCHEMA = { 'sell_profit_offset': {'type': 'number'}, 'ignore_roi_if_buy_signal': {'type': 'boolean'}, 'ignore_buying_expired_candle_after': {'type': 'number'}, + 'trading_mode': {'type': 'string', 'enum': TRADING_MODES}, + 'collateral_type': {'type': 'string', 'enum': COLLATERAL_TYPES}, 'bot_name': {'type': 'string'}, 'unfilledtimeout': { 'type': 'object', @@ -193,7 +197,7 @@ CONF_SCHEMA = { 'required': ['price_side'] }, 'custom_price_max_distance_ratio': { - 'type': 'number', 'minimum': 0.0 + 'type': 'number', 'minimum': 0.0 }, 'order_types': { 'type': 'object', diff --git a/freqtrade/enums/rpcmessagetype.py b/freqtrade/enums/rpcmessagetype.py index 4e3f693e5..663b37b83 100644 --- a/freqtrade/enums/rpcmessagetype.py +++ b/freqtrade/enums/rpcmessagetype.py @@ -5,15 +5,21 @@ 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' PROTECTION_TRIGGER = 'protection_trigger' PROTECTION_TRIGGER_GLOBAL = 'protection_trigger_global' + SHORT = 'short' + SHORT_FILL = 'short_fill' + SHORT_CANCEL = 'short_cancel' + def __repr__(self): return self.value diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 10bf0a753..f711bc258 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -804,8 +804,14 @@ class Exchange: rate_for_order = self.price_to_precision(pair, rate) if needs_price else None self._lev_prep(pair, leverage) - order = self._api.create_order(pair, ordertype, side, - amount, rate_for_order, params) + order = self._api.create_order( + pair, + ordertype, + side, + amount, + rate_for_order, + params + ) self._log_exchange_response('create_order', order) return order diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index ddb4b148f..bb7e06e8a 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -7,7 +7,7 @@ import traceback from datetime import datetime, time, timezone from math import isclose from threading import Lock -from typing import Any, Dict, List, Optional +from typing import Any, Dict, List, Optional, Tuple import arrow from schedule import Scheduler @@ -17,7 +17,8 @@ from freqtrade.configuration import validate_config_consistency from freqtrade.data.converter import order_book_to_dataframe from freqtrade.data.dataprovider import DataProvider from freqtrade.edge import Edge -from freqtrade.enums import RPCMessageType, SellType, State, TradingMode +from freqtrade.enums import (Collateral, RPCMessageType, SellType, SignalDirection, State, + TradingMode) from freqtrade.exceptions import (DependencyException, ExchangeError, InsufficientFundsError, InvalidOrderException, PricingError) from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds @@ -101,14 +102,19 @@ class FreqtradeBot(LoggingMixin): initial_state = self.config.get('initial_state') self.state = State[initial_state.upper()] if initial_state else State.STOPPED - # Protect sell-logic from forcesell and vice versa + # Protect exit-logic from forcesell and vice versa self._exit_lock = Lock() LoggingMixin.__init__(self, logger, timeframe_to_seconds(self.strategy.timeframe)) + self.trading_mode: TradingMode = TradingMode.SPOT + self.collateral_type: Optional[Collateral] = None + if 'trading_mode' in self.config: self.trading_mode = TradingMode(self.config['trading_mode']) - else: - self.trading_mode = TradingMode.SPOT + + if 'collateral_type' in self.config: + self.collateral_type = Collateral(self.config['collateral_type']) + self._schedule = Scheduler() if self.trading_mode == TradingMode.FUTURES: @@ -194,7 +200,7 @@ class FreqtradeBot(LoggingMixin): # Protect from collisions with forceexit. # Without this, freqtrade my try to recreate stoploss_on_exchange orders - # while selling is in process, since telegram messages arrive in an different thread. + # while exiting is in process, since telegram messages arrive in an different thread. with self._exit_lock: trades = Trade.get_open_trades() # First process current opened trades (positions) @@ -305,21 +311,26 @@ class FreqtradeBot(LoggingMixin): trades: List[Trade] = Trade.get_closed_trades_without_assigned_fees() for trade in trades: - - if not trade.is_open and not trade.fee_updated('sell'): + if not trade.is_open and not trade.fee_updated(trade.exit_side): # Get sell fee - order = trade.select_order('sell', False) + order = trade.select_order(trade.exit_side, False) if order: - logger.info(f"Updating sell-fee on trade {trade} for order {order.order_id}.") + logger.info( + f"Updating {trade.exit_side}-fee on trade {trade}" + f"for order {order.order_id}." + ) self.update_trade_state(trade, order.order_id, stoploss_order=order.ft_order_side == 'stoploss') trades: List[Trade] = Trade.get_open_trades_without_assigned_fees() for trade in trades: - if trade.is_open and not trade.fee_updated('buy'): - order = trade.select_order('buy', False) + if trade.is_open and not trade.fee_updated(trade.enter_side): + order = trade.select_order(trade.enter_side, False) if order: - logger.info(f"Updating buy-fee on trade {trade} for order {order.order_id}.") + logger.info( + f"Updating {trade.enter_side}-fee on trade {trade}" + f"for order {order.order_id}." + ) self.update_trade_state(trade, order.order_id) def handle_insufficient_funds(self, trade: Trade): @@ -327,8 +338,8 @@ class FreqtradeBot(LoggingMixin): Determine if we ever opened a exiting order for this trade. If not, try update entering fees - otherwise "refind" the open order we obviously lost. """ - sell_order = trade.select_order('sell', None) - if sell_order: + exit_order = trade.select_order(trade.exit_side, None) + if exit_order: self.refind_lost_order(trade) else: self.reupdate_enter_order_fees(trade) @@ -338,10 +349,11 @@ class FreqtradeBot(LoggingMixin): Get buy order from database, and try to reupdate. Handles trades where the initial fee-update did not work. """ - logger.info(f"Trying to reupdate buy fees for {trade}") - order = trade.select_order('buy', False) + logger.info(f"Trying to reupdate {trade.enter_side} fees for {trade}") + order = trade.select_order(trade.enter_side, False) if order: - logger.info(f"Updating buy-fee on trade {trade} for order {order.order_id}.") + logger.info( + f"Updating {trade.enter_side}-fee on trade {trade} for order {order.order_id}.") self.update_trade_state(trade, order.order_id) def refind_lost_order(self, trade): @@ -357,7 +369,7 @@ class FreqtradeBot(LoggingMixin): if not order.ft_is_open: logger.debug(f"Order {order} is no longer open.") continue - if order.ft_order_side == 'buy': + if order.ft_order_side == trade.enter_side: # Skip buy side - this is handled by reupdate_enter_order_fees continue try: @@ -367,7 +379,7 @@ class FreqtradeBot(LoggingMixin): if fo and fo['status'] == 'open': # Assume this as the open stoploss order trade.stoploss_order_id = order.order_id - elif order.ft_order_side == 'sell': + elif order.ft_order_side == trade.exit_side: if fo and fo['status'] == 'open': # Assume this as the open order trade.open_order_id = order.order_id @@ -456,7 +468,9 @@ class FreqtradeBot(LoggingMixin): # running get_signal on historical data fetched (signal, enter_tag) = self.strategy.get_entry_signal( - pair, self.strategy.timeframe, analyzed_df + pair, + self.strategy.timeframe, + analyzed_df ) if signal: @@ -465,19 +479,31 @@ class FreqtradeBot(LoggingMixin): 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)): - # TODO-lev: Does the below need to be adjusted for shorts? - if self._check_depth_of_market_buy(pair, bid_check_dom): - # TODO-lev: pass in "enter" as side. - - return self.execute_entry(pair, stake_amount, enter_tag=enter_tag) + if self._check_depth_of_market(pair, bid_check_dom, side=signal): + return self.execute_entry( + pair, + stake_amount, + enter_tag=enter_tag, + is_short=(signal == SignalDirection.SHORT) + ) else: return False - return self.execute_entry(pair, stake_amount, enter_tag=enter_tag) + return self.execute_entry( + pair, + stake_amount, + enter_tag=enter_tag, + is_short=(signal == SignalDirection.SHORT) + ) 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: SignalDirection + ) -> bool: """ Checks depth of market before executing a buy """ @@ -487,9 +513,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 == SignalDirection.LONG else order_book_asks + exit_side = order_book_asks if side == SignalDirection.LONG 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}, Direction: {side.value}" 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]}." @@ -501,21 +535,65 @@ class FreqtradeBot(LoggingMixin): logger.info(f"Bids to asks delta for {pair} does not satisfy condition.") return False - def execute_entry(self, pair: str, stake_amount: float, price: Optional[float] = None, - forcebuy: bool = False, enter_tag: Optional[str] = None) -> bool: + def leverage_prep( + self, + pair: str, + open_rate: float, + amount: float, + leverage: float, + is_short: bool + ) -> Tuple[float, Optional[float]]: + + interest_rate = 0.0 + isolated_liq = None + + # TODO-lev: Uncomment once liq and interest merged in + # if TradingMode == TradingMode.MARGIN: + # interest_rate = self.exchange.get_interest_rate( + # pair=pair, + # open_rate=open_rate, + # is_short=is_short + # ) + + # if self.collateral_type == Collateral.ISOLATED: + + # isolated_liq = liquidation_price( + # exchange_name=self.exchange.name, + # trading_mode=self.trading_mode, + # open_rate=open_rate, + # amount=amount, + # leverage=leverage, + # is_short=is_short + # ) + + return interest_rate, isolated_liq + + def execute_entry( + self, + pair: str, + stake_amount: float, + price: Optional[float] = None, + forcebuy: bool = False, + leverage: float = 1.0, + is_short: bool = False, + enter_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'] + [side, name] = ['sell', 'Short'] if is_short else ['buy', 'Long'] + if price: enter_limit_requested = price else: # Calculate price - proposed_enter_rate = self.exchange.get_rate(pair, refresh=True, side="buy") + proposed_enter_rate = self.exchange.get_rate(pair, refresh=True, side=side) custom_entry_price = strategy_safe_wrapper(self.strategy.custom_entry_price, default_retval=proposed_enter_rate)( pair=pair, current_time=datetime.now(timezone.utc), @@ -524,10 +602,14 @@ class FreqtradeBot(LoggingMixin): enter_limit_requested = self.get_valid_price(custom_entry_price, proposed_enter_rate) if not enter_limit_requested: - raise PricingError('Could not determine buy price.') + raise PricingError(f'Could not determine {side} price.') - min_stake_amount = self.exchange.get_min_pair_stake_amount(pair, enter_limit_requested, - self.strategy.stoploss) + min_stake_amount = self.exchange.get_min_pair_stake_amount( + pair, + enter_limit_requested, + self.strategy.stoploss, + leverage=leverage + ) if not self.edge: max_stake_amount = self.wallets.get_available_stake_amount() @@ -543,10 +625,12 @@ class FreqtradeBot(LoggingMixin): if not stake_amount: return False - logger.info(f"Buy signal found: about create a new trade for {pair} with stake_amount: " - f"{stake_amount} ...") + logger.info( + f"{name} signal found: about create a new trade for {pair} with stake_amount: " + f"{stake_amount} ..." + ) - amount = stake_amount / enter_limit_requested + amount = (stake_amount / enter_limit_requested) * leverage order_type = self.strategy.order_types['buy'] if forcebuy: # Forcebuy can define a different ordertype @@ -558,15 +642,21 @@ class FreqtradeBot(LoggingMixin): if not strategy_safe_wrapper(self.strategy.confirm_trade_entry, default_retval=True)( pair=pair, order_type=order_type, amount=amount, rate=enter_limit_requested, time_in_force=time_in_force, current_time=datetime.now(timezone.utc), - side='long' + side='short' if is_short else 'long' ): 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=enter_limit_requested, - time_in_force=time_in_force) - order_obj = Order.parse_from_ccxt_object(order, pair, 'buy') + order = self.exchange.create_order( + pair=pair, + ordertype=order_type, + side=side, + amount=amount, + rate=enter_limit_requested, + time_in_force=time_in_force, + leverage=leverage + ) + order_obj = Order.parse_from_ccxt_object(order, pair, side) order_id = order['id'] order_status = order.get('status', None) @@ -579,17 +669,17 @@ class FreqtradeBot(LoggingMixin): # return false if the order is not filled if float(order['filled']) == 0: - logger.warning('Buy %s order with time in force %s for %s is %s by %s.' + logger.warning('%s %s order with time in force %s for %s is %s by %s.' ' zero amount is fulfilled.', - order_tif, order_type, pair, order_status, self.exchange.name) + name, order_tif, order_type, pair, order_status, self.exchange.name) return False else: # the order is partially fulfilled # in case of IOC orders we can check immediately # if the order is fulfilled fully or partially - logger.warning('Buy %s order with time in force %s for %s is %s by %s.' + logger.warning('%s %s order with time in force %s for %s is %s by %s.' ' %s amount fulfilled out of %s (%s remaining which is canceled).', - order_tif, order_type, pair, order_status, self.exchange.name, + name, order_tif, order_type, pair, order_status, self.exchange.name, order['filled'], order['amount'], order['remaining'] ) stake_amount = order['cost'] @@ -602,6 +692,14 @@ class FreqtradeBot(LoggingMixin): amount = safe_value_fallback(order, 'filled', 'amount') enter_limit_filled_price = safe_value_fallback(order, 'average', 'price') + interest_rate, isolated_liq = self.leverage_prep( + leverage=leverage, + pair=pair, + amount=amount, + open_rate=enter_limit_filled_price, + is_short=is_short + ) + # Fee is applied twice because we make a LIMIT_BUY and LIMIT_SELL fee = self.exchange.get_fee(symbol=pair, taker_or_maker='maker') open_date = datetime.now(timezone.utc) @@ -627,6 +725,10 @@ class FreqtradeBot(LoggingMixin): # TODO-lev: compatibility layer for buy_tag (!) buy_tag=enter_tag, timeframe=timeframe_to_minutes(self.config['timeframe']), + leverage=leverage, + is_short=is_short, + interest_rate=interest_rate, + isolated_liq=isolated_liq, trading_mode=self.trading_mode, funding_fees=funding_fees ) @@ -652,7 +754,7 @@ class FreqtradeBot(LoggingMixin): """ 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, @@ -673,11 +775,11 @@ class FreqtradeBot(LoggingMixin): """ Sends rpc notification when a entry order cancel occurred. """ - current_rate = self.exchange.get_rate(trade.pair, refresh=False, side="buy") - + current_rate = self.exchange.get_rate(trade.pair, refresh=False, side=trade.enter_side) + 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, @@ -696,9 +798,10 @@ class FreqtradeBot(LoggingMixin): self.rpc.send_msg(msg) def _notify_enter_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, @@ -752,6 +855,7 @@ class FreqtradeBot(LoggingMixin): logger.debug('Handling %s ...', trade) (enter, exit_) = (False, False) + exit_signal_type = "exit_short" if trade.is_short else "exit_long" # TODO-lev: change to use_exit_signal, ignore_roi_if_enter_signal if (self.config.get('use_sell_signal', True) or @@ -762,15 +866,16 @@ class FreqtradeBot(LoggingMixin): (enter, exit_) = self.strategy.get_exit_signal( trade.pair, self.strategy.timeframe, - analyzed_df, is_short=trade.is_short + analyzed_df, + is_short=trade.is_short ) - # TODO-lev: side should depend on trade side. - exit_rate = self.exchange.get_rate(trade.pair, refresh=True, side="sell") + logger.debug('checking exit') + exit_rate = self.exchange.get_rate(trade.pair, refresh=True, side=trade.exit_side) if self._check_and_execute_exit(trade, exit_rate, enter, exit_): return True - logger.debug('Found no sell signal for %s.', trade) + logger.debug(f'Found no {exit_signal_type} signal for %s.', trade) return False def create_stoploss_order(self, trade: Trade, stop_price: float) -> bool: @@ -855,7 +960,10 @@ class FreqtradeBot(LoggingMixin): # 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() @@ -880,11 +988,11 @@ class FreqtradeBot(LoggingMixin): # if trailing stoploss is enabled we check if stoploss value has changed # in which case we cancel stoploss order and put another one with new # value immediately - self.handle_trailing_stoploss_on_exchange(trade, stoploss_order, side=trade.exit_side) + self.handle_trailing_stoploss_on_exchange(trade, stoploss_order) return False - def handle_trailing_stoploss_on_exchange(self, trade: Trade, order: dict, side: str) -> None: + def handle_trailing_stoploss_on_exchange(self, trade: Trade, order: dict) -> None: """ Check to see if stoploss on exchange should be updated in case of trailing stoploss on exchange @@ -892,7 +1000,7 @@ class FreqtradeBot(LoggingMixin): :param order: Current on exchange stoploss order :return: None """ - if self.exchange.stoploss_adjust(trade.stop_loss, order, side): + if self.exchange.stoploss_adjust(trade.stop_loss, order, side=trade.exit_side): # we check if the update is necessary update_beat = self.strategy.order_types.get('stoploss_on_exchange_interval', 60) if (datetime.utcnow() - trade.stoploss_last_update).total_seconds() >= update_beat: @@ -918,7 +1026,11 @@ class FreqtradeBot(LoggingMixin): Check and execute trade exit """ should_exit: SellCheckTuple = self.strategy.should_exit( - trade, exit_rate, datetime.now(timezone.utc), enter=enter, exit_=exit_, + trade, + exit_rate, + datetime.now(timezone.utc), + enter=enter, + exit_=exit_, force_stoploss=self.edge.stoploss(trade.pair) if self.edge else 0 ) @@ -959,24 +1071,23 @@ class FreqtradeBot(LoggingMixin): continue fully_cancelled = self.update_trade_state(trade, trade.open_order_id, order) + is_entering = order['side'] == trade.enter_side + not_closed = order['status'] == 'open' or fully_cancelled + side = trade.enter_side if is_entering else trade.exit_side + timed_out = self._check_timed_out(side, order) + time_method = 'check_sell_timeout' if order['side'] == 'sell' else 'check_buy_timeout' - if (order['side'] == 'buy' and (order['status'] == 'open' or fully_cancelled) and ( - fully_cancelled - or self._check_timed_out('buy', order) - or strategy_safe_wrapper(self.strategy.check_buy_timeout, - default_retval=False)(pair=trade.pair, - trade=trade, - order=order))): - self.handle_cancel_enter(trade, order, constants.CANCEL_REASON['TIMEOUT']) - - elif (order['side'] == 'sell' and (order['status'] == 'open' or fully_cancelled) and ( - fully_cancelled - or self._check_timed_out('sell', order) - or strategy_safe_wrapper(self.strategy.check_sell_timeout, - default_retval=False)(pair=trade.pair, - trade=trade, - order=order))): - self.handle_cancel_exit(trade, order, constants.CANCEL_REASON['TIMEOUT']) + if not_closed and (fully_cancelled or timed_out or ( + strategy_safe_wrapper(getattr(self.strategy, time_method), default_retval=False)( + pair=trade.pair, + trade=trade, + order=order + ) + )): + if is_entering: + self.handle_cancel_enter(trade, order, constants.CANCEL_REASON['TIMEOUT']) + else: + self.handle_cancel_exit(trade, order, constants.CANCEL_REASON['TIMEOUT']) def cancel_all_open_orders(self) -> None: """ @@ -991,10 +1102,10 @@ class FreqtradeBot(LoggingMixin): logger.info('Cannot query order for %s due to %s', trade, traceback.format_exc()) continue - if order['side'] == 'buy': + if order['side'] == trade.enter_side: self.handle_cancel_enter(trade, order, constants.CANCEL_REASON['ALL_CANCELLED']) - elif order['side'] == 'sell': + elif order['side'] == trade.exit_side: self.handle_cancel_exit(trade, order, constants.CANCEL_REASON['ALL_CANCELLED']) Trade.commit() @@ -1016,7 +1127,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 unexitable trade.") return False corder = self.exchange.cancel_order_with_result(trade.open_order_id, trade.pair, trade.amount) @@ -1031,12 +1142,16 @@ class FreqtradeBot(LoggingMixin): corder = order reason = constants.CANCEL_REASON['CANCELLED_ON_EXCHANGE'] - logger.info('Buy order %s for %s.', reason, trade) + side = trade.enter_side.capitalize() + logger.info('%s order %s for %s.', side, 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( + '%s order fully cancelled. Removing %s from database.', + side, trade + ) # if trade is not partially completed, just delete the trade trade.delete() was_trade_fully_canceled = True @@ -1054,11 +1169,11 @@ class FreqtradeBot(LoggingMixin): self.update_trade_state(trade, trade.open_order_id, corder) trade.open_order_id = None - logger.info('Partial buy order timeout for %s.', trade) + logger.info('Partial %s order timeout for %s.', trade.enter_side, trade) reason += f", {constants.CANCEL_REASON['PARTIALLY_FILLED']}" self.wallets.update() - self._notify_enter_cancel(trade, order_type=self.strategy.order_types['buy'], + self._notify_enter_cancel(trade, order_type=self.strategy.order_types[trade.enter_side], reason=reason) return was_trade_fully_canceled @@ -1076,12 +1191,13 @@ class FreqtradeBot(LoggingMixin): trade.amount) trade.update_order(co) except InvalidOrderException: - logger.exception(f"Could not cancel sell order {trade.open_order_id}") + logger.exception( + f"Could not cancel {trade.exit_side} order {trade.open_order_id}") return 'error cancelling order' - logger.info('Sell order %s for %s.', reason, trade) + logger.info('%s order %s for %s.', trade.exit_side.capitalize(), reason, trade) else: reason = constants.CANCEL_REASON['CANCELLED_ON_EXCHANGE'] - logger.info('Sell order %s for %s.', reason, trade) + logger.info('%s order %s for %s.', trade.exit_side.capitalize(), reason, trade) trade.update_order(order) trade.close_rate = None @@ -1098,7 +1214,7 @@ class FreqtradeBot(LoggingMixin): self.wallets.update() self._notify_exit_cancel( trade, - order_type=self.strategy.order_types['sell'], + order_type=self.strategy.order_types[trade.exit_side], reason=reason ) return reason @@ -1129,7 +1245,12 @@ class FreqtradeBot(LoggingMixin): raise DependencyException( f"Not enough amount to exit trade. Trade-amount: {amount}, Wallet: {wallet_amount}") - def execute_trade_exit(self, trade: Trade, limit: float, sell_reason: SellCheckTuple) -> bool: + def execute_trade_exit( + self, + trade: Trade, + limit: float, + sell_reason: SellCheckTuple, # TODO-lev update to exit_reason + ) -> bool: """ Executes a trade exit for the given trade and limit :param trade: Trade instance @@ -1137,13 +1258,13 @@ class FreqtradeBot(LoggingMixin): :param sell_reason: Reason the sell was triggered :return: True if it succeeds (supported) False (not supported) """ - sell_type = 'sell' # TODO-lev: Update to exit + exit_type = 'sell' # TODO-lev: Update to exit if sell_reason.sell_type in (SellType.STOP_LOSS, SellType.TRAILING_STOP_LOSS): - sell_type = 'stoploss' + exit_type = 'stoploss' # if stoploss is on exchange and we are on dry_run mode, # we consider the sell price stop price - if self.config['dry_run'] and sell_type == 'stoploss' \ + if self.config['dry_run'] and exit_type == 'stoploss' \ and self.strategy.order_types['stoploss_on_exchange']: limit = trade.stop_loss @@ -1167,7 +1288,7 @@ class FreqtradeBot(LoggingMixin): except InvalidOrderException: logger.exception(f"Could not cancel stoploss order {trade.stoploss_order_id}") - order_type = self.strategy.order_types[sell_type] + order_type = self.strategy.order_types[exit_type] if sell_reason.sell_type == SellType.EMERGENCY_SELL: # Emergency sells (default to market!) order_type = self.strategy.order_types.get("emergencysell", "market") @@ -1177,7 +1298,7 @@ class FreqtradeBot(LoggingMixin): order_type = self.strategy.order_types.get("forcesell", order_type) amount = self._safe_exit_amount(trade.pair, trade.amount) - time_in_force = self.strategy.order_time_in_force['sell'] + time_in_force = self.strategy.order_time_in_force['sell'] # TODO-lev update to exit if not strategy_safe_wrapper(self.strategy.confirm_trade_exit, default_retval=True)( pair=trade.pair, trade=trade, order_type=order_type, amount=amount, rate=limit, @@ -1191,7 +1312,7 @@ class FreqtradeBot(LoggingMixin): order = self.exchange.create_order( pair=trade.pair, ordertype=order_type, - side="sell", + side=trade.exit_side, amount=amount, rate=limit, time_in_force=time_in_force @@ -1202,7 +1323,7 @@ class FreqtradeBot(LoggingMixin): self.handle_insufficient_funds(trade) return False - order_obj = Order.parse_from_ccxt_object(order, trade.pair, 'sell') + order_obj = Order.parse_from_ccxt_object(order, trade.pair, trade.exit_side) trade.orders.append(order_obj) trade.open_order_id = order['id'] @@ -1230,7 +1351,7 @@ class FreqtradeBot(LoggingMixin): profit_trade = trade.calc_profit(rate=profit_rate) # Use cached rates here - it was updated seconds ago. current_rate = self.exchange.get_rate( - trade.pair, refresh=False, side="sell") if not fill else None + trade.pair, refresh=False, side=trade.exit_side) if not fill else None profit_ratio = trade.calc_profit_ratio(profit_rate) gain = "profit" if profit_ratio > 0 else "loss" @@ -1275,7 +1396,7 @@ class FreqtradeBot(LoggingMixin): profit_rate = trade.close_rate if trade.close_rate else trade.close_rate_requested profit_trade = trade.calc_profit(rate=profit_rate) - current_rate = self.exchange.get_rate(trade.pair, refresh=False, side="sell") + current_rate = self.exchange.get_rate(trade.pair, refresh=False, side=trade.exit_side) profit_ratio = trade.calc_profit_ratio(profit_rate) gain = "profit" if profit_ratio > 0 else "loss" @@ -1390,7 +1511,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-lev: won't be in "base"(quote) currency for shorts + # TODO-lev: won't be in base 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: diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index bbb390e75..5496628f4 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -506,7 +506,6 @@ class LocalTrade(): lower_stop = new_loss < self.stop_loss # stop losses only walk up, never down!, - # TODO-lev # ? But adding more to a leveraged trade would create a lower liquidation price, # ? decreasing the minimum stoploss if (higher_stop and not self.is_short) or (lower_stop and self.is_short): diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index a22a0b6b8..94541218c 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -840,28 +840,32 @@ class IStrategy(ABC, HyperStrategyMixin): else: logger.warning("CustomStoploss function did not return valid stoploss") - if self.trailing_stop and trade.stop_loss < (low or current_rate): + sl_lower_long = (trade.stop_loss < (low or current_rate) and not trade.is_short) + sl_higher_short = (trade.stop_loss > (high or current_rate) and trade.is_short) + if self.trailing_stop and (sl_lower_long or sl_higher_short): # trailing stoploss handling sl_offset = self.trailing_stop_positive_offset # Make sure current_profit is calculated using high for backtesting. - # TODO-lev: Check this function - high / low usage must be inversed for short trades! - high_profit = current_profit if not high else trade.calc_profit_ratio(high) + bound = low if trade.is_short else high + bound_profit = current_profit if not bound else trade.calc_profit_ratio(bound) # Don't update stoploss if trailing_only_offset_is_reached is true. - if not (self.trailing_only_offset_is_reached and high_profit < sl_offset): + if not (self.trailing_only_offset_is_reached and bound_profit < sl_offset): # Specific handling for trailing_stop_positive - if self.trailing_stop_positive is not None and high_profit > sl_offset: + if self.trailing_stop_positive is not None and bound_profit > sl_offset: stop_loss_value = self.trailing_stop_positive logger.debug(f"{trade.pair} - Using positive stoploss: {stop_loss_value} " f"offset: {sl_offset:.4g} profit: {current_profit:.4f}%") - trade.adjust_stop_loss(high or current_rate, stop_loss_value) + trade.adjust_stop_loss(bound or current_rate, stop_loss_value) + sl_higher_short = (trade.stop_loss >= (low or current_rate) and not trade.is_short) + sl_lower_long = ((trade.stop_loss <= (high or current_rate) and trade.is_short)) # evaluate if the stoploss was hit if stoploss is not on exchange # in Dry-Run, this handles stoploss logic as well, as the logic will not be different to # regular stoploss handling. - if ((trade.stop_loss >= (low or current_rate)) and + if ((sl_higher_short or sl_lower_long) and (not self.order_types.get('stoploss_on_exchange') or self.config['dry_run'])): sell_type = SellType.STOP_LOSS @@ -870,12 +874,18 @@ class IStrategy(ABC, HyperStrategyMixin): if trade.initial_stop_loss != trade.stop_loss: sell_type = SellType.TRAILING_STOP_LOSS logger.debug( - f"{trade.pair} - HIT STOP: current price at {(low or current_rate):.6f}, " + f"{trade.pair} - HIT STOP: current price at " + f"{((high if trade.is_short else low) or current_rate):.6f}, " f"stoploss is {trade.stop_loss:.6f}, " f"initial stoploss was at {trade.initial_stop_loss:.6f}, " f"trade opened at {trade.open_rate:.6f}") + new_stoploss = ( + trade.stop_loss + trade.initial_stop_loss + if trade.is_short else + trade.stop_loss - trade.initial_stop_loss + ) logger.debug(f"{trade.pair} - Trailing stop saved " - f"{trade.stop_loss - trade.initial_stop_loss:.6f}") + f"{new_stoploss:.6f}") return SellCheckTuple(sell_type=sell_type) diff --git a/freqtrade/templates/sample_strategy.py b/freqtrade/templates/sample_strategy.py index 80fa7cdae..13df9c2a8 100644 --- a/freqtrade/templates/sample_strategy.py +++ b/freqtrade/templates/sample_strategy.py @@ -58,6 +58,8 @@ class SampleStrategy(IStrategy): # Hyperoptable parameters buy_rsi = IntParameter(low=1, high=50, default=30, space='buy', optimize=True, load=True) sell_rsi = IntParameter(low=50, high=100, default=70, space='sell', optimize=True, load=True) + short_rsi = IntParameter(low=51, high=100, default=70, space='sell', optimize=True, load=True) + exit_short_rsi = IntParameter(low=1, high=50, default=30, space='buy', optimize=True, load=True) # Optimal timeframe for the strategy. timeframe = '5m' @@ -354,6 +356,16 @@ class SampleStrategy(IStrategy): ), 'enter_long'] = 1 + dataframe.loc[ + ( + # Signal: RSI crosses above 70 + (qtpylib.crossed_above(dataframe['rsi'], self.short_rsi.value)) & + (dataframe['tema'] > dataframe['bb_middleband']) & # Guard: tema above BB middle + (dataframe['tema'] < dataframe['tema'].shift(1)) & # Guard: tema is falling + (dataframe['volume'] > 0) # Make sure Volume is not 0 + ), + 'enter_short'] = 1 + return dataframe def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: @@ -371,5 +383,18 @@ class SampleStrategy(IStrategy): (dataframe['tema'] < dataframe['tema'].shift(1)) & # Guard: tema is falling (dataframe['volume'] > 0) # Make sure Volume is not 0 ), + 'exit_long'] = 1 + + dataframe.loc[ + ( + # Signal: RSI crosses above 30 + (qtpylib.crossed_above(dataframe['rsi'], self.exit_short_rsi.value)) & + # Guard: tema below BB middle + (dataframe['tema'] <= dataframe['bb_middleband']) & + (dataframe['tema'] > dataframe['tema'].shift(1)) & # Guard: tema is raising + (dataframe['volume'] > 0) # Make sure Volume is not 0 + ), + 'exit_short'] = 1 + return dataframe diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index 4e07e299c..d8218c82a 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -903,7 +903,7 @@ def test_hyperopt_list(mocker, capsys, caplog, saved_hyperopt_results, tmpdir): mocker.patch( 'freqtrade.optimize.hyperopt_tools.HyperoptTools._test_hyperopt_results_exist', return_value=True - ) + ) def fake_iterator(*args, **kwargs): yield from [saved_hyperopt_results] @@ -1309,9 +1309,10 @@ def test_start_list_data(testdatadir, capsys): @pytest.mark.usefixtures("init_persistence") +# TODO-lev: Short trades? def test_show_trades(mocker, fee, capsys, caplog): mocker.patch("freqtrade.persistence.init_db") - create_mock_trades(fee) + create_mock_trades(fee, False) args = [ "show-trades", "--db-url", diff --git a/tests/conftest.py b/tests/conftest.py index 7eec8d171..2c6297d57 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -209,8 +209,14 @@ def get_patched_worker(mocker, config) -> Worker: return Worker(args=None, config=config) -def patch_get_signal(freqtrade: FreqtradeBot, enter_long=True, exit_long=False, - enter_short=False, exit_short=False, enter_tag: Optional[str] = None) -> None: +def patch_get_signal( + freqtrade: FreqtradeBot, + enter_long=True, + exit_long=False, + enter_short=False, + exit_short=False, + enter_tag: Optional[str] = None +) -> None: """ :param mocker: mocker to patch IStrategy class :param value: which value IStrategy.get_signal() must return @@ -241,7 +247,7 @@ def patch_get_signal(freqtrade: FreqtradeBot, enter_long=True, exit_long=False, freqtrade.exchange.refresh_latest_ohlcv = lambda p: None -def create_mock_trades(fee, use_db: bool = True): +def create_mock_trades(fee, is_short: bool, use_db: bool = True): """ Create some fake trades ... """ @@ -252,22 +258,22 @@ def create_mock_trades(fee, use_db: bool = True): LocalTrade.add_bt_trade(trade) # Simulate dry_run entries - trade = mock_trade_1(fee) + trade = mock_trade_1(fee, is_short) add_trade(trade) - trade = mock_trade_2(fee) + trade = mock_trade_2(fee, is_short) add_trade(trade) - trade = mock_trade_3(fee) + trade = mock_trade_3(fee, is_short) add_trade(trade) - trade = mock_trade_4(fee) + trade = mock_trade_4(fee, is_short) add_trade(trade) - trade = mock_trade_5(fee) + trade = mock_trade_5(fee, is_short) add_trade(trade) - trade = mock_trade_6(fee) + trade = mock_trade_6(fee, is_short) add_trade(trade) if use_db: @@ -286,22 +292,22 @@ def create_mock_trades_with_leverage(fee, use_db: bool = True): LocalTrade.add_bt_trade(trade) # Simulate dry_run entries - trade = mock_trade_1(fee) + trade = mock_trade_1(fee, False) add_trade(trade) - trade = mock_trade_2(fee) + trade = mock_trade_2(fee, False) add_trade(trade) - trade = mock_trade_3(fee) + trade = mock_trade_3(fee, False) add_trade(trade) - trade = mock_trade_4(fee) + trade = mock_trade_4(fee, False) add_trade(trade) - trade = mock_trade_5(fee) + trade = mock_trade_5(fee, False) add_trade(trade) - trade = mock_trade_6(fee) + trade = mock_trade_6(fee, False) add_trade(trade) trade = short_trade(fee) @@ -324,7 +330,7 @@ def create_mock_trades_usdt(fee, use_db: bool = True): else: LocalTrade.add_bt_trade(trade) - # Simulate dry_run entries + # Simulate dry_run entries trade = mock_trade_usdt_1(fee) add_trade(trade) @@ -2297,6 +2303,7 @@ def limit_sell_order_usdt_open(): 'timestamp': arrow.utcnow().int_timestamp, 'price': 2.20, 'amount': 30.0, + 'cost': 66.0, 'filled': 0.0, 'remaining': 30.0, 'status': 'open' @@ -2342,3 +2349,27 @@ def market_sell_order_usdt(): 'remaining': 0.0, 'status': 'closed' } + + +@pytest.fixture(scope='function') +def limit_order(limit_buy_order_usdt, limit_sell_order_usdt): + return { + 'buy': limit_buy_order_usdt, + 'sell': limit_sell_order_usdt + } + + +@pytest.fixture(scope='function') +def market_order(market_buy_order_usdt, market_sell_order_usdt): + return { + 'buy': market_buy_order_usdt, + 'sell': market_sell_order_usdt + } + + +@pytest.fixture(scope='function') +def limit_order_open(limit_buy_order_usdt_open, limit_sell_order_usdt_open): + return { + 'buy': limit_buy_order_usdt_open, + 'sell': limit_sell_order_usdt_open + } diff --git a/tests/conftest_trades.py b/tests/conftest_trades.py index cf3c970f6..fe88064a3 100644 --- a/tests/conftest_trades.py +++ b/tests/conftest_trades.py @@ -6,12 +6,24 @@ from freqtrade.persistence.models import Order, Trade MOCK_TRADE_COUNT = 6 -def mock_order_1(): +def enter_side(is_short: bool): + return "sell" if is_short else "buy" + + +def exit_side(is_short: bool): + return "buy" if is_short else "sell" + + +def direc(is_short: bool): + return "short" if is_short else "long" + + +def mock_order_1(is_short: bool): return { - 'id': '1234', + 'id': f'1234_{direc(is_short)}', 'symbol': 'ETH/BTC', 'status': 'closed', - 'side': 'buy', + 'side': enter_side(is_short), 'type': 'limit', 'price': 0.123, 'amount': 123.0, @@ -20,7 +32,7 @@ def mock_order_1(): } -def mock_trade_1(fee): +def mock_trade_1(fee, is_short: bool): trade = Trade( pair='ETH/BTC', stake_amount=0.001, @@ -32,21 +44,22 @@ def mock_trade_1(fee): open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=17), open_rate=0.123, exchange='binance', - open_order_id='dry_run_buy_12345', + open_order_id=f'dry_run_buy_{direc(is_short)}_12345', strategy='StrategyTestV3', timeframe=5, + is_short=is_short ) - o = Order.parse_from_ccxt_object(mock_order_1(), 'ETH/BTC', 'buy') + o = Order.parse_from_ccxt_object(mock_order_1(is_short), 'ETH/BTC', enter_side(is_short)) trade.orders.append(o) return trade -def mock_order_2(): +def mock_order_2(is_short: bool): return { - 'id': '1235', + 'id': f'1235_{direc(is_short)}', 'symbol': 'ETC/BTC', 'status': 'closed', - 'side': 'buy', + 'side': enter_side(is_short), 'type': 'limit', 'price': 0.123, 'amount': 123.0, @@ -55,12 +68,12 @@ def mock_order_2(): } -def mock_order_2_sell(): +def mock_order_2_sell(is_short: bool): return { - 'id': '12366', + 'id': f'12366_{direc(is_short)}', 'symbol': 'ETC/BTC', 'status': 'closed', - 'side': 'sell', + 'side': exit_side(is_short), 'type': 'limit', 'price': 0.128, 'amount': 123.0, @@ -69,7 +82,7 @@ def mock_order_2_sell(): } -def mock_trade_2(fee): +def mock_trade_2(fee, is_short: bool): """ Closed trade... """ @@ -82,30 +95,31 @@ def mock_trade_2(fee): fee_close=fee.return_value, open_rate=0.123, close_rate=0.128, - close_profit=0.005, - close_profit_abs=0.000584127, + close_profit=-0.005 if is_short else 0.005, + close_profit_abs=-0.005584127 if is_short else 0.000584127, exchange='binance', is_open=False, - open_order_id='dry_run_sell_12345', + open_order_id=f'dry_run_sell_{direc(is_short)}_12345', strategy='StrategyTestV3', timeframe=5, sell_reason='sell_signal', open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20), close_date=datetime.now(tz=timezone.utc) - timedelta(minutes=2), + is_short=is_short ) - o = Order.parse_from_ccxt_object(mock_order_2(), 'ETC/BTC', 'buy') + o = Order.parse_from_ccxt_object(mock_order_2(is_short), 'ETC/BTC', enter_side(is_short)) trade.orders.append(o) - o = Order.parse_from_ccxt_object(mock_order_2_sell(), 'ETC/BTC', 'sell') + o = Order.parse_from_ccxt_object(mock_order_2_sell(is_short), 'ETC/BTC', exit_side(is_short)) trade.orders.append(o) return trade -def mock_order_3(): +def mock_order_3(is_short: bool): return { - 'id': '41231a12a', + 'id': f'41231a12a_{direc(is_short)}', 'symbol': 'XRP/BTC', 'status': 'closed', - 'side': 'buy', + 'side': enter_side(is_short), 'type': 'limit', 'price': 0.05, 'amount': 123.0, @@ -114,12 +128,12 @@ def mock_order_3(): } -def mock_order_3_sell(): +def mock_order_3_sell(is_short: bool): return { - 'id': '41231a666a', + 'id': f'41231a666a_{direc(is_short)}', 'symbol': 'XRP/BTC', 'status': 'closed', - 'side': 'sell', + 'side': exit_side(is_short), 'type': 'stop_loss_limit', 'price': 0.06, 'average': 0.06, @@ -129,7 +143,7 @@ def mock_order_3_sell(): } -def mock_trade_3(fee): +def mock_trade_3(fee, is_short: bool): """ Closed trade """ @@ -142,8 +156,8 @@ def mock_trade_3(fee): fee_close=fee.return_value, open_rate=0.05, close_rate=0.06, - close_profit=0.01, - close_profit_abs=0.000155, + close_profit=-0.01 if is_short else 0.01, + close_profit_abs=-0.001155 if is_short else 0.000155, exchange='binance', is_open=False, strategy='StrategyTestV3', @@ -151,20 +165,21 @@ def mock_trade_3(fee): sell_reason='roi', open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20), close_date=datetime.now(tz=timezone.utc), + is_short=is_short ) - o = Order.parse_from_ccxt_object(mock_order_3(), 'XRP/BTC', 'buy') + o = Order.parse_from_ccxt_object(mock_order_3(is_short), 'XRP/BTC', enter_side(is_short)) trade.orders.append(o) - o = Order.parse_from_ccxt_object(mock_order_3_sell(), 'XRP/BTC', 'sell') + o = Order.parse_from_ccxt_object(mock_order_3_sell(is_short), 'XRP/BTC', exit_side(is_short)) trade.orders.append(o) return trade -def mock_order_4(): +def mock_order_4(is_short: bool): return { - 'id': 'prod_buy_12345', + 'id': f'prod_buy_{direc(is_short)}_12345', 'symbol': 'ETC/BTC', 'status': 'open', - 'side': 'buy', + 'side': enter_side(is_short), 'type': 'limit', 'price': 0.123, 'amount': 123.0, @@ -173,7 +188,7 @@ def mock_order_4(): } -def mock_trade_4(fee): +def mock_trade_4(fee, is_short: bool): """ Simulate prod entry """ @@ -188,21 +203,22 @@ def mock_trade_4(fee): is_open=True, open_rate=0.123, exchange='binance', - open_order_id='prod_buy_12345', + open_order_id=f'prod_buy_{direc(is_short)}_12345', strategy='StrategyTestV3', timeframe=5, + is_short=is_short ) - o = Order.parse_from_ccxt_object(mock_order_4(), 'ETC/BTC', 'buy') + o = Order.parse_from_ccxt_object(mock_order_4(is_short), 'ETC/BTC', enter_side(is_short)) trade.orders.append(o) return trade -def mock_order_5(): +def mock_order_5(is_short: bool): return { - 'id': 'prod_buy_3455', + 'id': f'prod_buy_{direc(is_short)}_3455', 'symbol': 'XRP/BTC', 'status': 'closed', - 'side': 'buy', + 'side': enter_side(is_short), 'type': 'limit', 'price': 0.123, 'amount': 123.0, @@ -211,12 +227,12 @@ def mock_order_5(): } -def mock_order_5_stoploss(): +def mock_order_5_stoploss(is_short: bool): return { - 'id': 'prod_stoploss_3455', + 'id': f'prod_stoploss_{direc(is_short)}_3455', 'symbol': 'XRP/BTC', 'status': 'open', - 'side': 'sell', + 'side': exit_side(is_short), 'type': 'stop_loss_limit', 'price': 0.123, 'amount': 123.0, @@ -225,7 +241,7 @@ def mock_order_5_stoploss(): } -def mock_trade_5(fee): +def mock_trade_5(fee, is_short: bool): """ Simulate prod entry with stoploss """ @@ -241,22 +257,23 @@ def mock_trade_5(fee): open_rate=0.123, exchange='binance', strategy='SampleStrategy', - stoploss_order_id='prod_stoploss_3455', + stoploss_order_id=f'prod_stoploss_{direc(is_short)}_3455', timeframe=5, + is_short=is_short ) - o = Order.parse_from_ccxt_object(mock_order_5(), 'XRP/BTC', 'buy') + o = Order.parse_from_ccxt_object(mock_order_5(is_short), 'XRP/BTC', enter_side(is_short)) trade.orders.append(o) - o = Order.parse_from_ccxt_object(mock_order_5_stoploss(), 'XRP/BTC', 'stoploss') + o = Order.parse_from_ccxt_object(mock_order_5_stoploss(is_short), 'XRP/BTC', 'stoploss') trade.orders.append(o) return trade -def mock_order_6(): +def mock_order_6(is_short: bool): return { - 'id': 'prod_buy_6', + 'id': f'prod_buy_{direc(is_short)}_6', 'symbol': 'LTC/BTC', 'status': 'closed', - 'side': 'buy', + 'side': enter_side(is_short), 'type': 'limit', 'price': 0.15, 'amount': 2.0, @@ -265,23 +282,23 @@ def mock_order_6(): } -def mock_order_6_sell(): +def mock_order_6_sell(is_short: bool): return { - 'id': 'prod_sell_6', + 'id': f'prod_sell_{direc(is_short)}_6', 'symbol': 'LTC/BTC', 'status': 'open', - 'side': 'sell', + 'side': exit_side(is_short), 'type': 'limit', - 'price': 0.20, + 'price': 0.15 if is_short else 0.20, 'amount': 2.0, 'filled': 0.0, 'remaining': 2.0, } -def mock_trade_6(fee): +def mock_trade_6(fee, is_short: bool): """ - Simulate prod entry with open sell order + Simulate prod entry with open exit order """ trade = Trade( pair='LTC/BTC', @@ -295,12 +312,12 @@ def mock_trade_6(fee): open_rate=0.15, exchange='binance', strategy='SampleStrategy', - open_order_id="prod_sell_6", + open_order_id=f"prod_sell_{direc(is_short)}_6", timeframe=5, ) - o = Order.parse_from_ccxt_object(mock_order_6(), 'LTC/BTC', 'buy') + o = Order.parse_from_ccxt_object(mock_order_6(is_short), 'LTC/BTC', enter_side(is_short)) trade.orders.append(o) - o = Order.parse_from_ccxt_object(mock_order_6_sell(), 'LTC/BTC', 'sell') + o = Order.parse_from_ccxt_object(mock_order_6_sell(is_short), 'LTC/BTC', exit_side(is_short)) trade.orders.append(o) return trade diff --git a/tests/data/test_btanalysis.py b/tests/data/test_btanalysis.py index e7b8c5b2f..94cea62eb 100644 --- a/tests/data/test_btanalysis.py +++ b/tests/data/test_btanalysis.py @@ -111,9 +111,10 @@ def test_load_backtest_data_multi(testdatadir): @pytest.mark.usefixtures("init_persistence") -def test_load_trades_from_db(default_conf, fee, mocker): +@pytest.mark.parametrize('is_short', [False, True]) +def test_load_trades_from_db(default_conf, fee, is_short, mocker): - create_mock_trades(fee) + create_mock_trades(fee, is_short) # remove init so it does not init again init_mock = mocker.patch('freqtrade.data.btanalysis.init_db', MagicMock()) diff --git a/tests/exchange/test_kraken.py b/tests/exchange/test_kraken.py index f0033b2d7..641d2f263 100644 --- a/tests/exchange/test_kraken.py +++ b/tests/exchange/test_kraken.py @@ -164,6 +164,8 @@ def test_get_balances_prod(default_conf, mocker): ccxt_exceptionhandlers(mocker, default_conf, api_mock, "kraken", "get_balances", "fetch_balance") +# TODO-lev: All these stoploss tests with shorts + @pytest.mark.parametrize('ordertype', ['market', 'limit']) @pytest.mark.parametrize('side,adjustedprice', [ diff --git a/tests/plugins/test_pairlist.py b/tests/plugins/test_pairlist.py index c6246dccb..93eebde82 100644 --- a/tests/plugins/test_pairlist.py +++ b/tests/plugins/test_pairlist.py @@ -679,7 +679,7 @@ def test_PerformanceFilter_lookback(mocker, whitelist_conf, fee, caplog) -> None assert pm.whitelist == ['ETH/BTC', 'TKN/BTC', 'XRP/BTC'] with time_machine.travel("2021-09-01 05:00:00 +00:00") as t: - create_mock_trades(fee) + create_mock_trades(fee, False) pm.refresh_pairlist() assert pm.whitelist == ['XRP/BTC'] assert log_has_re(r'Removing pair .* since .* is below .*', caplog) diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 2f8fb9b85..0d349abf3 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -289,7 +289,8 @@ def test_rpc_daily_profit(default_conf, update, ticker, fee, rpc._rpc_daily_profit(0, stake_currency, fiat_display_currency) -def test_rpc_trade_history(mocker, default_conf, markets, fee): +@pytest.mark.parametrize('is_short', [True, False]) +def test_rpc_trade_history(mocker, default_conf, markets, fee, is_short): mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( 'freqtrade.exchange.Exchange', @@ -297,7 +298,7 @@ def test_rpc_trade_history(mocker, default_conf, markets, fee): ) freqtradebot = get_patched_freqtradebot(mocker, default_conf) - create_mock_trades(fee) + create_mock_trades(fee, is_short) rpc = RPC(freqtradebot) rpc._fiat_converter = CryptoToFiatConverter() trades = rpc._rpc_trade_history(2) @@ -314,7 +315,8 @@ def test_rpc_trade_history(mocker, default_conf, markets, fee): assert trades['trades'][0]['pair'] == 'XRP/BTC' -def test_rpc_delete_trade(mocker, default_conf, fee, markets, caplog): +@pytest.mark.parametrize('is_short', [True, False]) +def test_rpc_delete_trade(mocker, default_conf, fee, markets, caplog, is_short): mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) stoploss_mock = MagicMock() cancel_mock = MagicMock() @@ -327,7 +329,7 @@ def test_rpc_delete_trade(mocker, default_conf, fee, markets, caplog): freqtradebot = get_patched_freqtradebot(mocker, default_conf) freqtradebot.strategy.order_types['stoploss_on_exchange'] = True - create_mock_trades(fee) + create_mock_trades(fee, is_short) rpc = RPC(freqtradebot) with pytest.raises(RPCException, match='invalid argument'): rpc._rpc_delete('200') diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 18a84a19e..c9ffb4f34 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -458,7 +458,8 @@ def test_api_balance(botclient, mocker, rpc_balance, tickers): assert 'starting_capital_ratio' in response -def test_api_count(botclient, mocker, ticker, fee, markets): +@pytest.mark.parametrize('is_short', [True, False]) +def test_api_count(botclient, mocker, ticker, fee, markets, is_short): ftbot, client = botclient patch_get_signal(ftbot) mocker.patch.multiple( @@ -475,7 +476,7 @@ def test_api_count(botclient, mocker, ticker, fee, markets): assert rc.json()["max"] == 1 # Create some test data - create_mock_trades(fee) + create_mock_trades(fee, is_short) rc = client_get(client, f"{BASE_URI}/count") assert_response(rc) assert rc.json()["current"] == 4 @@ -556,7 +557,8 @@ def test_api_daily(botclient, mocker, ticker, fee, markets): assert rc.json()['data'][0]['date'] == str(datetime.utcnow().date()) -def test_api_trades(botclient, mocker, fee, markets): +@pytest.mark.parametrize('is_short', [True, False]) +def test_api_trades(botclient, mocker, fee, markets, is_short): ftbot, client = botclient patch_get_signal(ftbot) mocker.patch.multiple( @@ -569,7 +571,7 @@ def test_api_trades(botclient, mocker, fee, markets): assert rc.json()['trades_count'] == 0 assert rc.json()['total_trades'] == 0 - create_mock_trades(fee) + create_mock_trades(fee, is_short) Trade.query.session.flush() rc = client_get(client, f"{BASE_URI}/trades") @@ -584,6 +586,7 @@ def test_api_trades(botclient, mocker, fee, markets): assert rc.json()['total_trades'] == 2 +# TODO-lev: @pytest.mark.parametrize('is_short', [True, False]) def test_api_trade_single(botclient, mocker, fee, ticker, markets): ftbot, client = botclient patch_get_signal(ftbot) @@ -596,7 +599,7 @@ def test_api_trade_single(botclient, mocker, fee, ticker, markets): assert_response(rc, 404) assert rc.json()['detail'] == 'Trade not found.' - create_mock_trades(fee) + create_mock_trades(fee, False) Trade.query.session.flush() rc = client_get(client, f"{BASE_URI}/trade/3") @@ -604,6 +607,7 @@ def test_api_trade_single(botclient, mocker, fee, ticker, markets): assert rc.json()['trade_id'] == 3 +# TODO-lev: @pytest.mark.parametrize('is_short', [True, False]) def test_api_delete_trade(botclient, mocker, fee, markets): ftbot, client = botclient patch_get_signal(ftbot) @@ -619,7 +623,7 @@ def test_api_delete_trade(botclient, mocker, fee, markets): # Error - trade won't exist yet. assert_response(rc, 502) - create_mock_trades(fee) + create_mock_trades(fee, False) ftbot.strategy.order_types['stoploss_on_exchange'] = True trades = Trade.query.all() @@ -695,6 +699,7 @@ def test_api_edge_disabled(botclient, mocker, ticker, fee, markets): @pytest.mark.usefixtures("init_persistence") +# TODO-lev: @pytest.mark.parametrize('is_short', [True, False]) def test_api_profit(botclient, mocker, ticker, fee, markets): ftbot, client = botclient patch_get_signal(ftbot) @@ -710,7 +715,7 @@ def test_api_profit(botclient, mocker, ticker, fee, markets): assert_response(rc, 200) assert rc.json()['trade_count'] == 0 - create_mock_trades(fee) + create_mock_trades(fee, False) # Simulate fulfilled LIMIT_BUY order for trade rc = client_get(client, f"{BASE_URI}/profit") @@ -746,7 +751,8 @@ def test_api_profit(botclient, mocker, ticker, fee, markets): @pytest.mark.usefixtures("init_persistence") -def test_api_stats(botclient, mocker, ticker, fee, markets,): +# TODO-lev: @pytest.mark.parametrize('is_short', [True, False]) +def test_api_stats(botclient, mocker, ticker, fee, markets): ftbot, client = botclient patch_get_signal(ftbot) mocker.patch.multiple( @@ -762,7 +768,7 @@ def test_api_stats(botclient, mocker, ticker, fee, markets,): assert 'durations' in rc.json() assert 'sell_reasons' in rc.json() - create_mock_trades(fee) + create_mock_trades(fee, False) rc = client_get(client, f"{BASE_URI}/stats") assert_response(rc, 200) @@ -820,6 +826,10 @@ def test_api_performance(botclient, fee): {'count': 1, 'pair': 'XRP/ETH', 'profit': -5.57, 'profit_abs': -0.1150375}] +# TODO-lev: @pytest.mark.parametrize('is_short,side', [ +# (True, "short"), +# (False, "long") +# ]) def test_api_status(botclient, mocker, ticker, fee, markets): ftbot, client = botclient patch_get_signal(ftbot) @@ -835,7 +845,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets): rc = client_get(client, f"{BASE_URI}/status") assert_response(rc, 200) assert rc.json() == [] - create_mock_trades(fee) + create_mock_trades(fee, False) rc = client_get(client, f"{BASE_URI}/status") assert_response(rc) @@ -888,7 +898,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets): 'is_open': True, 'max_rate': ANY, 'min_rate': ANY, - 'open_order_id': 'dry_run_buy_12345', + 'open_order_id': 'dry_run_buy_long_12345', 'open_rate_requested': ANY, 'open_trade_value': 15.1668225, 'sell_reason': None, diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 791797ceb..c81fda101 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -33,6 +33,7 @@ class DummyCls(Telegram): """ Dummy class for testing the Telegram @authorized_only decorator """ + def __init__(self, rpc: RPC, config) -> None: super().__init__(rpc, config) self.state = {'called': False} @@ -479,8 +480,9 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee, assert '*Best Performing:* `ETH/BTC: 6.20%`' in msg_mock.call_args_list[-1][0][0] +@pytest.mark.parametrize('is_short', [True, False]) def test_telegram_stats(default_conf, update, ticker, ticker_sell_up, fee, - limit_buy_order, limit_sell_order, mocker) -> None: + limit_buy_order, limit_sell_order, mocker, is_short) -> None: mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) mocker.patch.multiple( 'freqtrade.exchange.Exchange', @@ -496,7 +498,7 @@ def test_telegram_stats(default_conf, update, ticker, ticker_sell_up, fee, msg_mock.reset_mock() # Create some test data - create_mock_trades(fee) + create_mock_trades(fee, is_short) telegram._stats(update=update, context=MagicMock()) assert msg_mock.call_count == 1 @@ -997,9 +999,9 @@ def test_count_handle(default_conf, update, ticker, fee, mocker) -> None: msg = ('
current max total stake\n--------- ----- -------------\n'
' 1 {} {}').format(
- default_conf['max_open_trades'],
- default_conf['stake_amount']
- )
+ default_conf['max_open_trades'],
+ default_conf['stake_amount']
+ )
assert msg in msg_mock.call_args_list[0][0][0]
@@ -1159,6 +1161,7 @@ def test_edge_enabled(edge_conf, update, mocker) -> None:
assert 'Winrate' not in msg_mock.call_args_list[0][0][0]
+# TODO-lev: @pytest.mark.parametrize('is_short', [True, False])
def test_telegram_trades(mocker, update, default_conf, fee):
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
@@ -1177,7 +1180,7 @@ def test_telegram_trades(mocker, update, default_conf, fee):
assert "" not in msg_mock.call_args_list[0][0][0]
msg_mock.reset_mock()
- create_mock_trades(fee)
+ create_mock_trades(fee, False)
context = MagicMock()
context.args = [5]
@@ -1191,6 +1194,7 @@ def test_telegram_trades(mocker, update, default_conf, fee):
msg_mock.call_args_list[0][0][0]))
+# TODO-lev: @pytest.mark.parametrize('is_short', [True, False])
def test_telegram_delete_trade(mocker, update, default_conf, fee):
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
@@ -1201,7 +1205,7 @@ def test_telegram_delete_trade(mocker, update, default_conf, fee):
assert "Trade-id not set." in msg_mock.call_args_list[0][0][0]
msg_mock.reset_mock()
- create_mock_trades(fee)
+ create_mock_trades(fee, False)
context = MagicMock()
context.args = [1]
diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py
index a9334c616..e735ab181 100644
--- a/tests/strategy/test_interface.py
+++ b/tests/strategy/test_interface.py
@@ -47,8 +47,8 @@ def test_returns_latest_signal(ohlcv_history):
mocked_history.loc[1, 'exit_long'] = 0
mocked_history.loc[1, 'enter_long'] = 1
- assert _STRATEGY.get_entry_signal('ETH/BTC', '5m', mocked_history
- ) == (SignalDirection.LONG, None)
+ assert _STRATEGY.get_entry_signal(
+ 'ETH/BTC', '5m', mocked_history) == (SignalDirection.LONG, None)
assert _STRATEGY.get_exit_signal('ETH/BTC', '5m', mocked_history) == (True, False)
assert _STRATEGY.get_exit_signal('ETH/BTC', '5m', mocked_history, True) == (False, False)
mocked_history.loc[1, 'exit_long'] = 0
diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py
index b77c42d15..6d784d9d1 100644
--- a/tests/test_freqtradebot.py
+++ b/tests/test_freqtradebot.py
@@ -11,7 +11,7 @@ import arrow
import pytest
from freqtrade.constants import CANCEL_REASON, MATH_CLOSE_PREC, UNLIMITED_STAKE_AMOUNT
-from freqtrade.enums import RPCMessageType, RunMode, SellType, State, TradingMode
+from freqtrade.enums import RPCMessageType, RunMode, SellType, SignalDirection, State
from freqtrade.exceptions import (DependencyException, ExchangeError, InsufficientFundsError,
InvalidOrderException, OperationalException, PricingError,
TemporaryError)
@@ -23,9 +23,9 @@ from freqtrade.worker import Worker
from tests.conftest import (create_mock_trades, get_patched_freqtradebot, get_patched_worker,
log_has, log_has_re, patch_edge, patch_exchange, patch_get_signal,
patch_wallet, patch_whitelist)
-from tests.conftest_trades import (MOCK_TRADE_COUNT, mock_order_1, mock_order_2, mock_order_2_sell,
- mock_order_3, mock_order_3_sell, mock_order_4,
- mock_order_5_stoploss, mock_order_6_sell)
+from tests.conftest_trades import (MOCK_TRADE_COUNT, enter_side, exit_side, mock_order_1,
+ mock_order_2, mock_order_2_sell, mock_order_3, mock_order_3_sell,
+ mock_order_4, mock_order_5_stoploss, mock_order_6_sell)
def patch_RPCManager(mocker) -> MagicMock:
@@ -41,6 +41,7 @@ def patch_RPCManager(mocker) -> MagicMock:
# Unit tests
+
def test_freqtradebot_state(mocker, default_conf_usdt, markets) -> None:
mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets))
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
@@ -195,12 +196,10 @@ def test_edge_overrides_stake_amount(mocker, edge_conf) -> None:
@pytest.mark.parametrize('buy_price_mult,ignore_strat_sl', [
- # Override stoploss
- (0.79, False),
- # Override strategy stoploss
- (0.85, True)
+ (0.79, False), # Override stoploss
+ (0.85, True), # Override strategy stoploss
])
-def test_edge_overrides_stoploss(limit_buy_order_usdt, fee, caplog, mocker,
+def test_edge_overrides_stoploss(limit_order, fee, caplog, mocker,
buy_price_mult, ignore_strat_sl, edge_conf) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
@@ -210,14 +209,14 @@ def test_edge_overrides_stoploss(limit_buy_order_usdt, fee, caplog, mocker,
# Strategy stoploss is -0.1 but Edge imposes a stoploss at -0.2
# Thus, if price falls 21%, stoploss should be triggered
#
- # mocking the ticker_usdt: price is falling ...
- buy_price = limit_buy_order_usdt['price']
+ # mocking the ticker: price is falling ...
+ enter_price = limit_order['buy']['price']
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=MagicMock(return_value={
- 'bid': buy_price * buy_price_mult,
- 'ask': buy_price * buy_price_mult,
- 'last': buy_price * buy_price_mult,
+ 'bid': enter_price * buy_price_mult,
+ 'ask': enter_price * buy_price_mult,
+ 'last': enter_price * buy_price_mult,
}),
get_fee=fee,
)
@@ -230,7 +229,8 @@ def test_edge_overrides_stoploss(limit_buy_order_usdt, fee, caplog, mocker,
freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
freqtrade.enter_positions()
trade = Trade.query.first()
- trade.update(limit_buy_order_usdt)
+ caplog.clear()
+ trade.update(limit_order['buy'])
#############################################
# stoploss shoud be hit
@@ -271,7 +271,12 @@ def test_total_open_trades_stakes(mocker, default_conf_usdt, ticker_usdt, fee) -
assert Trade.total_open_trades_stakes() == 120.0
-def test_create_trade(default_conf_usdt, ticker_usdt, limit_buy_order_usdt, fee, mocker) -> None:
+@pytest.mark.parametrize("is_short,open_rate", [
+ (False, 2.0),
+ (True, 2.2)
+])
+def test_create_trade(default_conf_usdt, ticker_usdt, limit_order,
+ fee, mocker, is_short, open_rate) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
@@ -284,10 +289,11 @@ def test_create_trade(default_conf_usdt, ticker_usdt, limit_buy_order_usdt, fee,
# Save state of current whitelist
whitelist = deepcopy(default_conf_usdt['exchange']['pair_whitelist'])
freqtrade = FreqtradeBot(default_conf_usdt)
- patch_get_signal(freqtrade)
+ patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
freqtrade.create_trade('ETH/USDT')
trade = Trade.query.first()
+ trade.is_short = is_short
assert trade is not None
assert trade.stake_amount == 60.0
assert trade.is_open
@@ -295,9 +301,9 @@ def test_create_trade(default_conf_usdt, ticker_usdt, limit_buy_order_usdt, fee,
assert trade.exchange == 'binance'
# Simulate fulfilled LIMIT_BUY order for trade
- trade.update(limit_buy_order_usdt)
+ trade.update(limit_order[enter_side(is_short)])
- assert trade.open_rate == 2.0
+ assert trade.open_rate == open_rate
assert trade.amount == 30.0
assert whitelist == default_conf_usdt['exchange']['pair_whitelist']
@@ -319,6 +325,7 @@ def test_create_trade_no_stake_amount(default_conf_usdt, ticker_usdt, fee, mocke
freqtrade.create_trade('ETH/USDT')
+@pytest.mark.parametrize("is_short", [False, True])
@pytest.mark.parametrize('stake_amount,create,amount_enough,max_open_trades', [
(5.0, True, True, 99),
(0.00005, True, False, 99),
@@ -326,27 +333,27 @@ def test_create_trade_no_stake_amount(default_conf_usdt, ticker_usdt, fee, mocke
(UNLIMITED_STAKE_AMOUNT, False, True, 0),
])
def test_create_trade_minimal_amount(
- default_conf_usdt, ticker_usdt, limit_buy_order_usdt_open, fee, mocker,
- stake_amount, create, amount_enough, max_open_trades, caplog
+ default_conf_usdt, ticker_usdt, limit_order_open, fee, mocker,
+ stake_amount, create, amount_enough, max_open_trades, caplog, is_short
) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
- buy_mock = MagicMock(return_value=limit_buy_order_usdt_open)
+ enter_mock = MagicMock(return_value=limit_order_open[enter_side(is_short)])
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt,
- create_order=buy_mock,
+ create_order=enter_mock,
get_fee=fee,
)
default_conf_usdt['max_open_trades'] = max_open_trades
freqtrade = FreqtradeBot(default_conf_usdt)
freqtrade.config['stake_amount'] = stake_amount
- patch_get_signal(freqtrade)
+ patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
if create:
assert freqtrade.create_trade('ETH/USDT')
if amount_enough:
- rate, amount = buy_mock.call_args[1]['rate'], buy_mock.call_args[1]['amount']
+ rate, amount = enter_mock.call_args[1]['rate'], enter_mock.call_args[1]['amount']
assert rate * amount <= default_conf_usdt['stake_amount']
else:
assert log_has_re(
@@ -416,7 +423,8 @@ def test_enter_positions_global_pairlock(default_conf_usdt, ticker_usdt, limit_b
assert log_has_re(message, caplog)
-def test_handle_protections(mocker, default_conf_usdt, fee):
+@pytest.mark.parametrize('is_short', [False, True])
+def test_handle_protections(mocker, default_conf_usdt, fee, is_short):
default_conf_usdt['protections'] = [
{"method": "CooldownPeriod", "stop_duration": 60},
{
@@ -431,7 +439,7 @@ def test_handle_protections(mocker, default_conf_usdt, fee):
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
freqtrade.protections._protection_handlers[1].global_stop = MagicMock(
return_value=(True, arrow.utcnow().shift(hours=1).datetime, "asdf"))
- create_mock_trades(fee)
+ create_mock_trades(fee, is_short)
freqtrade.handle_protections('ETC/BTC')
send_msg_mock = freqtrade.rpc.send_msg
assert send_msg_mock.call_count == 2
@@ -450,7 +458,8 @@ def test_create_trade_no_signal(default_conf_usdt, fee, mocker) -> None:
)
default_conf_usdt['stake_amount'] = 10
freqtrade = FreqtradeBot(default_conf_usdt)
- patch_get_signal(freqtrade, enter_long=False)
+ # patch_get_signal(freqtrade, enter_long=False)
+ patch_get_signal(freqtrade, enter_long=False, exit_long=False)
Trade.query = MagicMock()
Trade.query.filter = MagicMock()
@@ -516,19 +525,24 @@ def test_create_trades_preopen(default_conf_usdt, ticker_usdt, fee, mocker,
assert len(trades) == 4
-def test_process_trade_creation(default_conf_usdt, ticker_usdt, limit_buy_order_usdt,
- limit_buy_order_usdt_open, fee, mocker, caplog) -> None:
+@pytest.mark.parametrize('is_short, open_rate', [
+ (False, 2.0),
+ (True, 2.02)
+])
+def test_process_trade_creation(default_conf_usdt, ticker_usdt, limit_order, limit_order_open,
+ is_short, open_rate, fee, mocker, caplog
+ ) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt,
- create_order=MagicMock(return_value=limit_buy_order_usdt_open),
- fetch_order=MagicMock(return_value=limit_buy_order_usdt),
+ create_order=MagicMock(return_value=limit_order_open[enter_side(is_short)]),
+ fetch_order=MagicMock(return_value=limit_order[enter_side(is_short)]),
get_fee=fee,
)
freqtrade = FreqtradeBot(default_conf_usdt)
- patch_get_signal(freqtrade)
+ patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
trades = Trade.query.filter(Trade.is_open.is_(True)).all()
assert not trades
@@ -543,11 +557,12 @@ def test_process_trade_creation(default_conf_usdt, ticker_usdt, limit_buy_order_
assert trade.is_open
assert trade.open_date is not None
assert trade.exchange == 'binance'
- assert trade.open_rate == 2.0
- assert trade.amount == 30.0
+ assert trade.open_rate == open_rate # TODO-lev: I think? That's what the ticker ask price is
+ assert isclose(trade.amount, 60 / open_rate)
assert log_has(
- 'Buy signal found: about create a new trade for ETH/USDT with stake_amount: 60.0 ...',
+ f'{"Short" if is_short else "Long"} signal found: about create a new trade for ETH/USDT '
+ 'with stake_amount: 60.0 ...',
caplog
)
@@ -695,148 +710,160 @@ def test_process_informative_pairs_added(default_conf_usdt, ticker_usdt, mocker)
assert ("ETH/USDT", default_conf_usdt["timeframe"]) in refresh_mock.call_args[0][0]
-def test_execute_entry(mocker, default_conf_usdt, fee, limit_buy_order_usdt,
- limit_buy_order_usdt_open) -> None:
+@pytest.mark.parametrize("is_short", [False, True])
+def test_execute_entry(mocker, default_conf_usdt, fee, limit_order,
+ limit_order_open, is_short) -> None:
+
+ open_order = limit_order_open[enter_side(is_short)]
+ order = limit_order[enter_side(is_short)]
+
patch_RPCManager(mocker)
patch_exchange(mocker)
freqtrade = FreqtradeBot(default_conf_usdt)
freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=False)
stake_amount = 2
bid = 0.11
- buy_rate_mock = MagicMock(return_value=bid)
- buy_mm = MagicMock(return_value=limit_buy_order_usdt_open)
+ enter_rate_mock = MagicMock(return_value=bid)
+ enter_mm = MagicMock(return_value=open_order)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
- get_rate=buy_rate_mock,
+ get_rate=enter_rate_mock,
fetch_ticker=MagicMock(return_value={
'bid': 1.9,
'ask': 2.2,
'last': 1.9
}),
- create_order=buy_mm,
+ create_order=enter_mm,
get_min_pair_stake_amount=MagicMock(return_value=1),
get_fee=fee,
)
pair = 'ETH/USDT'
- assert not freqtrade.execute_entry(pair, stake_amount)
- assert buy_rate_mock.call_count == 1
- assert buy_mm.call_count == 0
+ assert not freqtrade.execute_entry(pair, stake_amount, is_short=is_short)
+ assert enter_rate_mock.call_count == 1
+ assert enter_mm.call_count == 0
assert freqtrade.strategy.confirm_trade_entry.call_count == 1
- buy_rate_mock.reset_mock()
+ enter_rate_mock.reset_mock()
- limit_buy_order_usdt_open['id'] = '22'
+ open_order['id'] = '22'
freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=True)
assert freqtrade.execute_entry(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]
+ assert enter_rate_mock.call_count == 1
+ assert enter_mm.call_count == 1
+ call_args = enter_mm.call_args_list[0][1]
assert call_args['pair'] == pair
assert call_args['rate'] == bid
assert call_args['amount'] == round(stake_amount / bid, 8)
- buy_rate_mock.reset_mock()
+ enter_rate_mock.reset_mock()
# Should create an open trade with an open order id
# As the order is not fulfilled yet
trade = Trade.query.first()
+ trade.is_short = is_short
assert trade
assert trade.is_open is True
assert trade.open_order_id == '22'
# Test calling with price
- limit_buy_order_usdt_open['id'] = '33'
+ open_order['id'] = '33'
fix_price = 0.06
- assert freqtrade.execute_entry(pair, stake_amount, fix_price)
+ assert freqtrade.execute_entry(pair, stake_amount, fix_price, is_short=is_short)
# Make sure get_rate wasn't called again
- assert buy_rate_mock.call_count == 0
+ assert enter_rate_mock.call_count == 0
- assert buy_mm.call_count == 2
- call_args = buy_mm.call_args_list[1][1]
+ assert enter_mm.call_count == 2
+ call_args = enter_mm.call_args_list[1][1]
assert call_args['pair'] == pair
assert call_args['rate'] == fix_price
assert call_args['amount'] == round(stake_amount / fix_price, 8)
# In case of closed order
- limit_buy_order_usdt['status'] = 'closed'
- limit_buy_order_usdt['price'] = 10
- limit_buy_order_usdt['cost'] = 100
- limit_buy_order_usdt['id'] = '444'
+ order['status'] = 'closed'
+ order['price'] = 10
+ order['cost'] = 100
+ order['id'] = '444'
mocker.patch('freqtrade.exchange.Exchange.create_order',
- MagicMock(return_value=limit_buy_order_usdt))
- assert freqtrade.execute_entry(pair, stake_amount)
+ MagicMock(return_value=order))
+ assert freqtrade.execute_entry(pair, stake_amount, is_short=is_short)
trade = Trade.query.all()[2]
+ trade.is_short = is_short
assert trade
assert trade.open_order_id is None
assert trade.open_rate == 10
assert trade.stake_amount == 100
# In case of rejected or expired order and partially filled
- limit_buy_order_usdt['status'] = 'expired'
- limit_buy_order_usdt['amount'] = 30.0
- limit_buy_order_usdt['filled'] = 20.0
- limit_buy_order_usdt['remaining'] = 10.00
- limit_buy_order_usdt['price'] = 0.5
- limit_buy_order_usdt['cost'] = 15.0
- limit_buy_order_usdt['id'] = '555'
+ order['status'] = 'expired'
+ order['amount'] = 30.0
+ order['filled'] = 20.0
+ order['remaining'] = 10.00
+ order['price'] = 0.5
+ order['cost'] = 15.0
+ order['id'] = '555'
mocker.patch('freqtrade.exchange.Exchange.create_order',
- MagicMock(return_value=limit_buy_order_usdt))
+ MagicMock(return_value=order))
assert freqtrade.execute_entry(pair, stake_amount)
trade = Trade.query.all()[3]
+ trade.is_short = is_short
assert trade
assert trade.open_order_id == '555'
assert trade.open_rate == 0.5
assert trade.stake_amount == 15.0
# Test with custom stake
- limit_buy_order_usdt['status'] = 'open'
- limit_buy_order_usdt['id'] = '556'
+ order['status'] = 'open'
+ order['id'] = '556'
freqtrade.strategy.custom_stake_amount = lambda **kwargs: 150.0
- assert freqtrade.execute_entry(pair, stake_amount)
+ assert freqtrade.execute_entry(pair, stake_amount, is_short=is_short)
trade = Trade.query.all()[4]
+ trade.is_short = is_short
assert trade
assert trade.stake_amount == 150
# Exception case
- limit_buy_order_usdt['id'] = '557'
+ order['id'] = '557'
freqtrade.strategy.custom_stake_amount = lambda **kwargs: 20 / 0
- assert freqtrade.execute_entry(pair, stake_amount)
+ assert freqtrade.execute_entry(pair, stake_amount, is_short=is_short)
trade = Trade.query.all()[5]
+ trade.is_short = is_short
assert trade
assert trade.stake_amount == 2.0
# In case of the order is rejected and not filled at all
- limit_buy_order_usdt['status'] = 'rejected'
- limit_buy_order_usdt['amount'] = 30.0
- limit_buy_order_usdt['filled'] = 0.0
- limit_buy_order_usdt['remaining'] = 30.0
- limit_buy_order_usdt['price'] = 0.5
- limit_buy_order_usdt['cost'] = 0.0
- limit_buy_order_usdt['id'] = '66'
+ order['status'] = 'rejected'
+ order['amount'] = 30.0
+ order['filled'] = 0.0
+ order['remaining'] = 30.0
+ order['price'] = 0.5
+ order['cost'] = 0.0
+ order['id'] = '66'
mocker.patch('freqtrade.exchange.Exchange.create_order',
- MagicMock(return_value=limit_buy_order_usdt))
+ MagicMock(return_value=order))
assert not freqtrade.execute_entry(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_entry(pair, stake_amount)
+ with pytest.raises(PricingError, match=f"Could not determine {enter_side(is_short)} price."):
+ freqtrade.execute_entry(pair, stake_amount, is_short=is_short)
# In case of custom entry price
mocker.patch('freqtrade.exchange.Exchange.get_rate', return_value=0.50)
- limit_buy_order_usdt['status'] = 'open'
- limit_buy_order_usdt['id'] = '5566'
+ order['status'] = 'open'
+ order['id'] = '5566'
freqtrade.strategy.custom_entry_price = lambda **kwargs: 0.508
- assert freqtrade.execute_entry(pair, stake_amount)
+ assert freqtrade.execute_entry(pair, stake_amount, is_short=is_short)
trade = Trade.query.all()[6]
+ trade.is_short = is_short
assert trade
assert trade.open_rate_requested == 0.508
# In case of custom entry price set to None
- limit_buy_order_usdt['status'] = 'open'
- limit_buy_order_usdt['id'] = '5567'
+
+ order['status'] = 'open'
+ order['id'] = '5567'
freqtrade.strategy.custom_entry_price = lambda **kwargs: None
mocker.patch.multiple(
@@ -844,22 +871,25 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_buy_order_usdt,
get_rate=MagicMock(return_value=10),
)
- assert freqtrade.execute_entry(pair, stake_amount)
+ assert freqtrade.execute_entry(pair, stake_amount, is_short=is_short)
trade = Trade.query.all()[7]
+ trade.is_short = is_short
assert trade
assert trade.open_rate_requested == 10
# In case of custom entry price not float type
- limit_buy_order_usdt['status'] = 'open'
- limit_buy_order_usdt['id'] = '5568'
+ order['status'] = 'open'
+ order['id'] = '5568'
freqtrade.strategy.custom_entry_price = lambda **kwargs: "string price"
- assert freqtrade.execute_entry(pair, stake_amount)
+ assert freqtrade.execute_entry(pair, stake_amount, is_short=is_short)
trade = Trade.query.all()[8]
+ trade.is_short = is_short
assert trade
assert trade.open_rate_requested == 10
-def test_execute_entry_confirm_error(mocker, default_conf_usdt, fee, limit_buy_order_usdt) -> None:
+@pytest.mark.parametrize("is_short", [False, True])
+def test_execute_entry_confirm_error(mocker, default_conf_usdt, fee, limit_order, is_short) -> None:
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
@@ -868,7 +898,7 @@ def test_execute_entry_confirm_error(mocker, default_conf_usdt, fee, limit_buy_o
'ask': 2.2,
'last': 1.9
}),
- create_order=MagicMock(return_value=limit_buy_order_usdt),
+ create_order=MagicMock(return_value=limit_order[enter_side(is_short)]),
get_rate=MagicMock(return_value=0.11),
get_min_pair_stake_amount=MagicMock(return_value=1),
get_fee=fee,
@@ -877,13 +907,14 @@ def test_execute_entry_confirm_error(mocker, default_conf_usdt, fee, limit_buy_o
pair = 'ETH/USDT'
freqtrade.strategy.confirm_trade_entry = MagicMock(side_effect=ValueError)
+ # TODO-lev: KeyError happens on short, why?
assert freqtrade.execute_entry(pair, stake_amount)
- limit_buy_order_usdt['id'] = '222'
+ limit_order[enter_side(is_short)]['id'] = '222'
freqtrade.strategy.confirm_trade_entry = MagicMock(side_effect=Exception)
assert freqtrade.execute_entry(pair, stake_amount)
- limit_buy_order_usdt['id'] = '2223'
+ limit_order[enter_side(is_short)]['id'] = '2223'
freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=True)
assert freqtrade.execute_entry(pair, stake_amount)
@@ -891,14 +922,16 @@ def test_execute_entry_confirm_error(mocker, default_conf_usdt, fee, limit_buy_o
assert not freqtrade.execute_entry(pair, stake_amount)
-def test_add_stoploss_on_exchange(mocker, default_conf_usdt, limit_buy_order_usdt) -> None:
+@pytest.mark.parametrize("is_short", [False, True])
+def test_add_stoploss_on_exchange(mocker, default_conf_usdt, limit_order, is_short) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
+ order = limit_order[enter_side(is_short)]
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_trade', MagicMock(return_value=True))
- mocker.patch('freqtrade.exchange.Exchange.fetch_order', return_value=limit_buy_order_usdt)
+ mocker.patch('freqtrade.exchange.Exchange.fetch_order', return_value=order)
mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[])
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount',
- return_value=limit_buy_order_usdt['amount'])
+ return_value=order['amount'])
stoploss = MagicMock(return_value={'id': 13434334})
mocker.patch('freqtrade.exchange.Binance.stoploss', stoploss)
@@ -907,6 +940,7 @@ def test_add_stoploss_on_exchange(mocker, default_conf_usdt, limit_buy_order_usd
freqtrade.strategy.order_types['stoploss_on_exchange'] = True
trade = MagicMock()
+ trade.is_short = is_short
trade.open_order_id = None
trade.stoploss_order_id = None
trade.is_open = True
@@ -918,9 +952,12 @@ def test_add_stoploss_on_exchange(mocker, default_conf_usdt, limit_buy_order_usd
assert trade.is_open is True
-def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog,
- limit_buy_order_usdt, limit_sell_order_usdt) -> None:
+@pytest.mark.parametrize("is_short", [False, True])
+def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_short,
+ limit_order) -> None:
stoploss = MagicMock(return_value={'id': 13434334})
+ enter_order = limit_order[enter_side(is_short)]
+ exit_order = limit_order[exit_side(is_short)]
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
@@ -931,8 +968,8 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog,
'last': 1.9
}),
create_order=MagicMock(side_effect=[
- {'id': limit_buy_order_usdt['id']},
- {'id': limit_sell_order_usdt['id']},
+ {'id': enter_order['id']},
+ {'id': exit_order['id']},
]),
get_fee=fee,
)
@@ -941,15 +978,17 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog,
stoploss=stoploss
)
freqtrade = FreqtradeBot(default_conf_usdt)
- patch_get_signal(freqtrade)
+ patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
# First case: when stoploss is not yet set but the order is open
# should get the stoploss order id immediately
# and should return false as no trade actually happened
trade = MagicMock()
+ trade.is_short = is_short
trade.is_open = True
trade.open_order_id = None
trade.stoploss_order_id = None
+ trade.is_short = is_short
assert freqtrade.handle_stoploss_on_exchange(trade) is False
assert stoploss.call_count == 1
@@ -988,6 +1027,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog,
caplog.clear()
freqtrade.enter_positions()
trade = Trade.query.first()
+ trade.is_short = is_short
trade.is_open = True
trade.open_order_id = None
trade.stoploss_order_id = 100
@@ -999,7 +1039,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog,
'type': 'stop_loss_limit',
'price': 3,
'average': 2,
- 'amount': limit_buy_order_usdt['amount'],
+ 'amount': enter_order['amount'],
})
mocker.patch('freqtrade.exchange.Binance.fetch_stoploss_order', stoploss_order_hit)
assert freqtrade.handle_stoploss_on_exchange(trade) is True
@@ -1038,9 +1078,12 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog,
assert stoploss.call_count == 0
-def test_handle_sle_cancel_cant_recreate(mocker, default_conf_usdt, fee, caplog,
- limit_buy_order_usdt, limit_sell_order_usdt) -> None:
+@pytest.mark.parametrize("is_short", [False, True])
+def test_handle_sle_cancel_cant_recreate(mocker, default_conf_usdt, fee, caplog, is_short,
+ limit_order) -> None:
# Sixth case: stoploss order was cancelled but couldn't create new one
+ enter_order = limit_order[enter_side(is_short)]
+ exit_order = limit_order[exit_side(is_short)]
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
@@ -1051,8 +1094,8 @@ def test_handle_sle_cancel_cant_recreate(mocker, default_conf_usdt, fee, caplog,
'last': 1.9
}),
create_order=MagicMock(side_effect=[
- {'id': limit_buy_order_usdt['id']},
- {'id': limit_sell_order_usdt['id']},
+ {'id': enter_order['id']},
+ {'id': exit_order['id']},
]),
get_fee=fee,
)
@@ -1062,10 +1105,11 @@ def test_handle_sle_cancel_cant_recreate(mocker, default_conf_usdt, fee, caplog,
stoploss=MagicMock(side_effect=ExchangeError()),
)
freqtrade = FreqtradeBot(default_conf_usdt)
- patch_get_signal(freqtrade)
+ patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
freqtrade.enter_positions()
trade = Trade.query.first()
+ trade.is_short = is_short
trade.is_open = True
trade.open_order_id = None
trade.stoploss_order_id = 100
@@ -1077,13 +1121,17 @@ def test_handle_sle_cancel_cant_recreate(mocker, default_conf_usdt, fee, caplog,
assert trade.is_open is True
-def test_create_stoploss_order_invalid_order(mocker, default_conf_usdt, caplog, fee,
- limit_buy_order_usdt_open, limit_sell_order_usdt):
+@pytest.mark.parametrize("is_short", [False, True])
+def test_create_stoploss_order_invalid_order(
+ mocker, default_conf_usdt, caplog, fee, is_short, limit_order, limit_order_open
+):
+ open_order = limit_order_open[enter_side(is_short)]
+ order = limit_order[exit_side(is_short)]
rpc_mock = patch_RPCManager(mocker)
patch_exchange(mocker)
create_order_mock = MagicMock(side_effect=[
- limit_buy_order_usdt_open,
- {'id': limit_sell_order_usdt['id']}
+ open_order,
+ {'id': order['id']}
])
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
@@ -1101,11 +1149,12 @@ def test_create_stoploss_order_invalid_order(mocker, default_conf_usdt, caplog,
stoploss=MagicMock(side_effect=InvalidOrderException()),
)
freqtrade = FreqtradeBot(default_conf_usdt)
- patch_get_signal(freqtrade)
+ patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
freqtrade.strategy.order_types['stoploss_on_exchange'] = True
freqtrade.enter_positions()
trade = Trade.query.first()
+ trade.is_short = is_short
caplog.clear()
freqtrade.create_stoploss_order(trade, 200)
assert trade.stoploss_order_id is None
@@ -1125,9 +1174,12 @@ def test_create_stoploss_order_invalid_order(mocker, default_conf_usdt, caplog,
assert rpc_mock.call_args_list[1][0][0]['order_type'] == 'market'
-def test_create_stoploss_order_insufficient_funds(mocker, default_conf_usdt, caplog, fee,
- limit_buy_order_usdt_open, limit_sell_order_usdt):
- sell_mock = MagicMock(return_value={'id': limit_sell_order_usdt['id']})
+@pytest.mark.parametrize("is_short", [False, True])
+def test_create_stoploss_order_insufficient_funds(
+ mocker, default_conf_usdt, caplog, fee, limit_order_open,
+ limit_order, is_short
+):
+ exit_order = limit_order[exit_side(is_short)]['id']
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
mock_insuf = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_insufficient_funds')
@@ -1139,8 +1191,8 @@ def test_create_stoploss_order_insufficient_funds(mocker, default_conf_usdt, cap
'last': 1.9
}),
create_order=MagicMock(side_effect=[
- limit_buy_order_usdt_open,
- sell_mock,
+ limit_order[enter_side(is_short)],
+ exit_order,
]),
get_fee=fee,
fetch_order=MagicMock(return_value={'status': 'canceled'}),
@@ -1149,11 +1201,12 @@ def test_create_stoploss_order_insufficient_funds(mocker, default_conf_usdt, cap
'freqtrade.exchange.Binance',
stoploss=MagicMock(side_effect=InsufficientFundsError()),
)
- patch_get_signal(freqtrade)
+ patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
freqtrade.strategy.order_types['stoploss_on_exchange'] = True
freqtrade.enter_positions()
trade = Trade.query.first()
+ trade.is_short = is_short
caplog.clear()
freqtrade.create_stoploss_order(trade, 200)
# stoploss_orderid was empty before
@@ -1168,11 +1221,18 @@ def test_create_stoploss_order_insufficient_funds(mocker, default_conf_usdt, cap
assert mock_insuf.call_count == 1
+@pytest.mark.parametrize("is_short,bid,ask,stop_price,amt,hang_price", [
+ (False, [4.38, 4.16], [4.4, 4.17], ['2.0805', 4.4 * 0.95], 27.39726027, 3),
+ (True, [1.09, 1.21], [1.1, 1.22], ['2.321', 1.09 * 1.05], 27.27272727, 1.5),
+])
@pytest.mark.usefixtures("init_persistence")
-def test_handle_stoploss_on_exchange_trailing(mocker, default_conf_usdt, fee,
- limit_buy_order_usdt, limit_sell_order_usdt) -> None:
+def test_handle_stoploss_on_exchange_trailing(
+ mocker, default_conf_usdt, fee, is_short, bid, ask, limit_order, stop_price, amt, hang_price
+) -> None:
# TODO-lev: test for short
# When trailing stoploss is set
+ enter_order = limit_order[enter_side(is_short)]
+ exit_order = limit_order[exit_side(is_short)]
stoploss = MagicMock(return_value={'id': 13434334})
patch_RPCManager(mocker)
mocker.patch.multiple(
@@ -1180,11 +1240,11 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf_usdt, fee,
fetch_ticker=MagicMock(return_value={
'bid': 2.19,
'ask': 2.2,
- 'last': 2.19
+ 'last': 2.19,
}),
create_order=MagicMock(side_effect=[
- {'id': limit_buy_order_usdt['id']},
- {'id': limit_sell_order_usdt['id']},
+ {'id': enter_order['id']},
+ {'id': exit_order['id']},
]),
get_fee=fee,
)
@@ -1206,15 +1266,16 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf_usdt, fee,
freqtrade.strategy.order_types['stoploss_on_exchange'] = True
# setting stoploss
- freqtrade.strategy.stoploss = -0.05
+ freqtrade.strategy.stoploss = 0.05 if is_short else -0.05
# setting stoploss_on_exchange_interval to 60 seconds
freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 60
- patch_get_signal(freqtrade)
+ patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
freqtrade.enter_positions()
trade = Trade.query.first()
+ trade.is_short = is_short
trade.is_open = True
trade.open_order_id = None
trade.stoploss_order_id = 100
@@ -1223,10 +1284,10 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf_usdt, fee,
'id': 100,
'status': 'open',
'type': 'stop_loss_limit',
- 'price': 3,
+ 'price': hang_price,
'average': 2,
'info': {
- 'stopPrice': '2.0805'
+ 'stopPrice': stop_price[0]
}
})
@@ -1240,9 +1301,9 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf_usdt, fee,
mocker.patch(
'freqtrade.exchange.Exchange.fetch_ticker',
MagicMock(return_value={
- 'bid': 4.38,
- 'ask': 4.4,
- 'last': 4.38
+ 'bid': bid[0],
+ 'ask': ask[0],
+ 'last': bid[0],
})
)
@@ -1258,7 +1319,7 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf_usdt, fee,
stoploss_order_mock.assert_not_called()
assert freqtrade.handle_trade(trade) is False
- assert trade.stop_loss == 4.4 * 0.95
+ assert trade.stop_loss == stop_price[1]
# setting stoploss_on_exchange_interval to 0 seconds
freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 0
@@ -1267,11 +1328,11 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf_usdt, fee,
cancel_order_mock.assert_called_once_with(100, 'ETH/USDT')
stoploss_order_mock.assert_called_once_with(
- amount=27.39726027,
+ amount=amt,
pair='ETH/USDT',
order_types=freqtrade.strategy.order_types,
- stop_price=4.4 * 0.95,
- side="sell",
+ stop_price=stop_price[1],
+ side=exit_side(is_short),
leverage=1.0
)
@@ -1279,18 +1340,20 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf_usdt, fee,
mocker.patch(
'freqtrade.exchange.Exchange.fetch_ticker',
MagicMock(return_value={
- 'bid': 4.16,
- 'ask': 4.17,
- 'last': 4.16
+ 'bid': bid[1],
+ 'ask': ask[1],
+ 'last': bid[1],
})
)
assert freqtrade.handle_trade(trade) is True
+@pytest.mark.parametrize("is_short", [False, True])
def test_handle_stoploss_on_exchange_trailing_error(
- mocker, default_conf_usdt, fee, caplog, limit_buy_order_usdt, limit_sell_order_usdt
+ mocker, default_conf_usdt, fee, caplog, limit_order, is_short
) -> None:
- # TODO-lev: test for short
+ enter_order = limit_order[enter_side(is_short)]
+ exit_order = limit_order[exit_side(is_short)]
# When trailing stoploss is set
stoploss = MagicMock(return_value={'id': 13434334})
patch_exchange(mocker)
@@ -1303,8 +1366,8 @@ def test_handle_stoploss_on_exchange_trailing_error(
'last': 1.9
}),
create_order=MagicMock(side_effect=[
- {'id': limit_buy_order_usdt['id']},
- {'id': limit_sell_order_usdt['id']},
+ {'id': enter_order['id']},
+ {'id': exit_order['id']},
]),
get_fee=fee,
)
@@ -1322,18 +1385,20 @@ def test_handle_stoploss_on_exchange_trailing_error(
freqtrade.strategy.order_types['stoploss_on_exchange'] = True
# setting stoploss
- freqtrade.strategy.stoploss = -0.05
+ freqtrade.strategy.stoploss = 0.05 if is_short else -0.05
# setting stoploss_on_exchange_interval to 60 seconds
freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 60
- patch_get_signal(freqtrade)
+ patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
freqtrade.enter_positions()
trade = Trade.query.first()
+ trade.is_short = is_short
trade.is_open = True
trade.open_order_id = None
trade.stoploss_order_id = "abcd"
trade.stop_loss = 0.2
trade.stoploss_last_update = arrow.utcnow().shift(minutes=-601).datetime.replace(tzinfo=None)
+ trade.is_short = is_short
stoploss_order_hanging = {
'id': "abcd",
@@ -1349,7 +1414,7 @@ def test_handle_stoploss_on_exchange_trailing_error(
side_effect=InvalidOrderException())
mocker.patch('freqtrade.exchange.Binance.fetch_stoploss_order',
return_value=stoploss_order_hanging)
- freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging, side="sell")
+ freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging)
assert log_has_re(r"Could not cancel stoploss order abcd for pair ETH/USDT.*", caplog)
# Still try to create order
@@ -1359,14 +1424,17 @@ def test_handle_stoploss_on_exchange_trailing_error(
caplog.clear()
cancel_mock = mocker.patch("freqtrade.exchange.Binance.cancel_stoploss_order", MagicMock())
mocker.patch("freqtrade.exchange.Binance.stoploss", side_effect=ExchangeError())
- freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging, side="sell")
+ freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging)
assert cancel_mock.call_count == 1
assert log_has_re(r"Could not create trailing stoploss order for pair ETH/USDT\..*", caplog)
-@pytest.mark.usefixtures("init_persistence")
+@pytest.mark.parametrize("is_short", [False, True])
def test_handle_stoploss_on_exchange_custom_stop(
- mocker, default_conf_usdt, fee, limit_buy_order_usdt, limit_sell_order_usdt) -> None:
+ mocker, default_conf_usdt, fee, is_short, limit_order
+) -> None:
+ enter_order = limit_order[enter_side(is_short)]
+ exit_order = limit_order[exit_side(is_short)]
# When trailing stoploss is set
# TODO-lev: test for short
stoploss = MagicMock(return_value={'id': 13434334})
@@ -1379,8 +1447,8 @@ def test_handle_stoploss_on_exchange_custom_stop(
'last': 1.9
}),
create_order=MagicMock(side_effect=[
- {'id': limit_buy_order_usdt['id']},
- {'id': limit_sell_order_usdt['id']},
+ {'id': enter_order['id']},
+ {'id': exit_order['id']},
]),
get_fee=fee,
)
@@ -1407,10 +1475,11 @@ def test_handle_stoploss_on_exchange_custom_stop(
# setting stoploss_on_exchange_interval to 60 seconds
freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 60
- patch_get_signal(freqtrade)
+ patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
freqtrade.enter_positions()
trade = Trade.query.first()
+ trade.is_short = is_short
trade.is_open = True
trade.open_order_id = None
trade.stoploss_order_id = 100
@@ -1435,9 +1504,9 @@ def test_handle_stoploss_on_exchange_custom_stop(
mocker.patch(
'freqtrade.exchange.Exchange.fetch_ticker',
MagicMock(return_value={
- 'bid': 4.38,
- 'ask': 4.4,
- 'last': 4.38
+ 'bid': 4.38 if not is_short else 1.9 / 2,
+ 'ask': 4.4 if not is_short else 2.2 / 2,
+ 'last': 4.38 if not is_short else 1.9 / 2,
})
)
@@ -1453,8 +1522,8 @@ def test_handle_stoploss_on_exchange_custom_stop(
stoploss_order_mock.assert_not_called()
assert freqtrade.handle_trade(trade) is False
- assert trade.stop_loss == 4.4 * 0.96
- assert trade.stop_loss_pct == -0.04
+ assert trade.stop_loss == 4.4 * 0.96 if not is_short else 1.1
+ assert trade.stop_loss_pct == -0.04 if not is_short else 0.04
# setting stoploss_on_exchange_interval to 0 seconds
freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 0
@@ -1462,12 +1531,13 @@ def test_handle_stoploss_on_exchange_custom_stop(
assert freqtrade.handle_stoploss_on_exchange(trade) is False
cancel_order_mock.assert_called_once_with(100, 'ETH/USDT')
+ # Long uses modified ask - offset, short modified bid + offset
stoploss_order_mock.assert_called_once_with(
- amount=31.57894736,
+ amount=trade.amount,
pair='ETH/USDT',
order_types=freqtrade.strategy.order_types,
- stop_price=4.4 * 0.96,
- side="sell",
+ stop_price=4.4 * 0.96 if not is_short else 0.95 * 1.04,
+ side=exit_side(is_short),
leverage=1.0
)
@@ -1483,9 +1553,12 @@ def test_handle_stoploss_on_exchange_custom_stop(
assert freqtrade.handle_trade(trade) is True
-def test_tsl_on_exchange_compatible_with_edge(
- mocker, edge_conf, fee, limit_buy_order_usdt, limit_sell_order_usdt) -> None:
- # TODO-lev: test for short
+def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, caplog,
+ limit_order) -> None:
+
+ enter_order = limit_order['buy']
+ exit_order = limit_order['sell']
+
# When trailing stoploss is set
stoploss = MagicMock(return_value={'id': 13434334})
patch_RPCManager(mocker)
@@ -1502,8 +1575,8 @@ def test_tsl_on_exchange_compatible_with_edge(
'last': 2.19
}),
create_order=MagicMock(side_effect=[
- {'id': limit_buy_order_usdt['id']},
- {'id': limit_sell_order_usdt['id']},
+ {'id': enter_order['id']},
+ {'id': exit_order['id']},
]),
get_fee=fee,
stoploss=stoploss,
@@ -1594,7 +1667,7 @@ def test_tsl_on_exchange_compatible_with_edge(
pair='NEO/BTC',
order_types=freqtrade.strategy.order_types,
stop_price=4.4 * 0.99,
- side="sell",
+ side='sell',
leverage=1.0
)
@@ -1622,11 +1695,47 @@ def test_enter_positions(mocker, default_conf_usdt, return_value, side_effect,
assert mock_ct.call_count == len(default_conf_usdt['exchange']['pair_whitelist'])
-def test_exit_positions_exception(mocker, default_conf_usdt, limit_buy_order_usdt, caplog) -> None:
+@pytest.mark.parametrize("is_short", [False, True])
+def test_exit_positions(
+ mocker, default_conf_usdt, limit_order, is_short, caplog
+) -> None:
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
- mocker.patch('freqtrade.exchange.Exchange.fetch_order', return_value=limit_buy_order_usdt)
+
+ mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_trade', MagicMock(return_value=True))
+ mocker.patch('freqtrade.exchange.Exchange.fetch_order',
+ return_value=limit_order[enter_side(is_short)])
+ mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[])
+ mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount',
+ return_value=limit_order[enter_side(is_short)]['amount'])
trade = MagicMock()
+ trade.is_short = is_short
+ trade.open_order_id = '123'
+ trade.open_fee = 0.001
+ trades = [trade]
+ n = freqtrade.exit_positions(trades)
+ assert n == 0
+ # Test amount not modified by fee-logic
+ assert not log_has(
+ 'Applying fee to amount for Trade {} from 30.0 to 90.81'.format(trade), caplog
+ )
+
+ mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount', return_value=90.81)
+ # test amount modified by fee-logic
+ n = freqtrade.exit_positions(trades)
+ assert n == 0
+
+
+@pytest.mark.parametrize("is_short", [False, True])
+def test_exit_positions_exception(
+ mocker, default_conf_usdt, limit_order, caplog, is_short
+) -> None:
+ freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
+ order = limit_order[enter_side(is_short)]
+ mocker.patch('freqtrade.exchange.Exchange.fetch_order', return_value=order)
+
+ trade = MagicMock()
+ trade.is_short = is_short
trade.open_order_id = None
trade.open_fee = 0.001
trade.pair = 'ETH/USDT'
@@ -1637,19 +1746,24 @@ def test_exit_positions_exception(mocker, default_conf_usdt, limit_buy_order_usd
'freqtrade.freqtradebot.FreqtradeBot.handle_trade',
side_effect=DependencyException()
)
+ caplog.clear()
n = freqtrade.exit_positions(trades)
assert n == 0
assert log_has('Unable to exit trade ETH/USDT: ', caplog)
-def test_update_trade_state(mocker, default_conf_usdt, limit_buy_order_usdt, caplog) -> None:
+@pytest.mark.parametrize("is_short", [False, True])
+def test_update_trade_state(
+ mocker, default_conf_usdt, limit_order, is_short, caplog
+) -> None:
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
+ order = limit_order[enter_side(is_short)]
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_trade', MagicMock(return_value=True))
- mocker.patch('freqtrade.exchange.Exchange.fetch_order', return_value=limit_buy_order_usdt)
+ mocker.patch('freqtrade.exchange.Exchange.fetch_order', return_value=order)
mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[])
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount',
- return_value=limit_buy_order_usdt['amount'])
+ return_value=order['amount'])
trade = Trade(
open_order_id=123,
@@ -1659,6 +1773,7 @@ def test_update_trade_state(mocker, default_conf_usdt, limit_buy_order_usdt, cap
open_date=arrow.utcnow().datetime,
amount=11,
exchange="binance",
+ is_short=is_short
)
assert not freqtrade.update_trade_state(trade, None)
assert log_has_re(r'Orderid for trade .* is empty.', caplog)
@@ -1669,7 +1784,7 @@ def test_update_trade_state(mocker, default_conf_usdt, limit_buy_order_usdt, cap
assert not log_has_re(r'Applying fee to .*', caplog)
caplog.clear()
assert trade.open_order_id is None
- assert trade.amount == limit_buy_order_usdt['amount']
+ assert trade.amount == order['amount']
trade.open_order_id = '123'
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount', return_value=90.81)
@@ -1687,12 +1802,16 @@ def test_update_trade_state(mocker, default_conf_usdt, limit_buy_order_usdt, cap
assert log_has_re('Found open order for.*', caplog)
+@pytest.mark.parametrize("is_short", [False, True])
@pytest.mark.parametrize('initial_amount,has_rounding_fee', [
(30.0 + 1e-14, True),
(8.0, False)
])
-def test_update_trade_state_withorderdict(default_conf_usdt, trades_for_order, limit_buy_order_usdt,
- fee, mocker, initial_amount, has_rounding_fee, caplog):
+def test_update_trade_state_withorderdict(
+ default_conf_usdt, trades_for_order, limit_order, fee, mocker, initial_amount,
+ has_rounding_fee, is_short, caplog
+):
+ order = limit_order[enter_side(is_short)]
trades_for_order[0]['amount'] = initial_amount
mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order)
# fetch_order should not be called!!
@@ -1711,22 +1830,26 @@ def test_update_trade_state_withorderdict(default_conf_usdt, trades_for_order, l
fee_close=fee.return_value,
open_order_id="123456",
is_open=True,
+ is_short=is_short
)
- freqtrade.update_trade_state(trade, '123456', limit_buy_order_usdt)
+ freqtrade.update_trade_state(trade, '123456', order)
assert trade.amount != amount
- assert trade.amount == limit_buy_order_usdt['amount']
+ assert trade.amount == order['amount']
if has_rounding_fee:
assert log_has_re(r'Applying fee on amount for .*', caplog)
-def test_update_trade_state_exception(mocker, default_conf_usdt,
- limit_buy_order_usdt, caplog) -> None:
+@pytest.mark.parametrize("is_short", [False, True])
+def test_update_trade_state_exception(mocker, default_conf_usdt, is_short, limit_order,
+ caplog) -> None:
+ order = limit_order[enter_side(is_short)]
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
- mocker.patch('freqtrade.exchange.Exchange.fetch_order', return_value=limit_buy_order_usdt)
+ mocker.patch('freqtrade.exchange.Exchange.fetch_order', return_value=order)
trade = MagicMock()
trade.open_order_id = '123'
trade.open_fee = 0.001
+ trade.is_short = is_short
# Test raise of OperationalException exception
mocker.patch(
@@ -1753,8 +1876,12 @@ def test_update_trade_state_orderexception(mocker, default_conf_usdt, caplog) ->
assert log_has(f'Unable to fetch order {trade.open_order_id}: ', caplog)
-def test_update_trade_state_sell(default_conf_usdt, trades_for_order, limit_sell_order_usdt_open,
- limit_sell_order_usdt, mocker):
+@pytest.mark.parametrize("is_short", [False, True])
+def test_update_trade_state_sell(
+ default_conf_usdt, trades_for_order, limit_order_open, limit_order, is_short, mocker,
+):
+ open_order = limit_order_open[exit_side(is_short)]
+ l_order = limit_order[exit_side(is_short)]
mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order)
# fetch_order should not be called!!
mocker.patch('freqtrade.exchange.Exchange.fetch_order', MagicMock(side_effect=ValueError))
@@ -1762,8 +1889,8 @@ def test_update_trade_state_sell(default_conf_usdt, trades_for_order, limit_sell
mocker.patch('freqtrade.wallets.Wallets.update', wallet_mock)
patch_exchange(mocker)
- amount = limit_sell_order_usdt["amount"]
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
+ amount = l_order["amount"]
wallet_mock.reset_mock()
trade = Trade(
pair='LTC/ETH',
@@ -1775,12 +1902,14 @@ def test_update_trade_state_sell(default_conf_usdt, trades_for_order, limit_sell
open_date=arrow.utcnow().datetime,
open_order_id="123456",
is_open=True,
+ interest_rate=0.0005,
+ is_short=is_short
)
- order = Order.parse_from_ccxt_object(limit_sell_order_usdt_open, 'LTC/ETH', 'sell')
+ order = Order.parse_from_ccxt_object(open_order, 'LTC/ETH', (enter_side(is_short)))
trade.orders.append(order)
assert order.status == 'open'
- freqtrade.update_trade_state(trade, trade.open_order_id, limit_sell_order_usdt)
- assert trade.amount == limit_sell_order_usdt['amount']
+ freqtrade.update_trade_state(trade, trade.open_order_id, l_order)
+ assert trade.amount == l_order['amount']
# Wallet needs to be updated after closing a limit-sell order to reenable buying
assert wallet_mock.call_count == 1
assert not trade.is_open
@@ -1788,78 +1917,96 @@ def test_update_trade_state_sell(default_conf_usdt, trades_for_order, limit_sell
assert order.status == 'closed'
-def test_handle_trade(default_conf_usdt, limit_buy_order_usdt, limit_sell_order_usdt_open,
- limit_sell_order_usdt, fee, mocker) -> None:
+@pytest.mark.parametrize('is_short,close_profit', [
+ (False, 0.09451372),
+ (True, 0.08635224),
+])
+def test_handle_trade(
+ default_conf_usdt, limit_order_open, limit_order, fee, mocker, is_short, close_profit
+) -> None:
+ open_order = limit_order_open[exit_side(is_short)]
+ enter_order = limit_order[enter_side(is_short)]
+ exit_order = limit_order[exit_side(is_short)]
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=MagicMock(return_value={
- 'bid': 1.9,
+ 'bid': 2.19,
'ask': 2.2,
- 'last': 1.9
+ 'last': 2.19
}),
create_order=MagicMock(side_effect=[
- limit_buy_order_usdt,
- limit_sell_order_usdt_open,
+ enter_order,
+ open_order,
]),
get_fee=fee,
)
freqtrade = FreqtradeBot(default_conf_usdt)
- patch_get_signal(freqtrade)
+ patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
freqtrade.enter_positions()
trade = Trade.query.first()
+ trade.is_short = is_short
assert trade
time.sleep(0.01) # Race condition fix
- trade.update(limit_buy_order_usdt)
+ trade.update(enter_order)
assert trade.is_open is True
freqtrade.wallets.update()
- patch_get_signal(freqtrade, enter_long=False, exit_long=True)
+ patch_get_signal(freqtrade, enter_long=False, exit_short=is_short, exit_long=not is_short)
assert freqtrade.handle_trade(trade) is True
- assert trade.open_order_id == limit_sell_order_usdt['id']
+ assert trade.open_order_id == exit_order['id']
- # Simulate fulfilled LIMIT_SELL order for trade
- trade.update(limit_sell_order_usdt)
+ # Simulate fulfilled LIMIT order for trade
+ trade.update(exit_order)
- assert trade.close_rate == 2.2
- assert trade.close_profit == 0.09451372
+ assert trade.close_rate == 2.0 if is_short else 2.2
+ assert trade.close_profit == close_profit
assert trade.calc_profit() == 5.685
assert trade.close_date is not None
-def test_handle_overlapping_signals(default_conf_usdt, ticker_usdt, limit_buy_order_usdt_open,
- fee, mocker) -> None:
+@ pytest.mark.parametrize("is_short", [False, True])
+def test_handle_overlapping_signals(
+ default_conf_usdt, ticker_usdt, limit_order_open, fee, mocker, is_short
+) -> None:
+ open_order = limit_order_open[exit_side(is_short)]
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt,
create_order=MagicMock(side_effect=[
- limit_buy_order_usdt_open,
+ open_order,
{'id': 1234553382},
]),
get_fee=fee,
)
freqtrade = FreqtradeBot(default_conf_usdt)
- patch_get_signal(freqtrade, enter_long=True, exit_long=True)
+ if is_short:
+ patch_get_signal(freqtrade, enter_long=False, enter_short=True, exit_short=True)
+ else:
+ patch_get_signal(freqtrade, enter_long=True, exit_long=True)
freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
freqtrade.enter_positions()
# Buy and Sell triggering, so doing nothing ...
trades = Trade.query.all()
+
nb_trades = len(trades)
assert nb_trades == 0
# Buy is triggering, so buying ...
- patch_get_signal(freqtrade)
+ patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
freqtrade.enter_positions()
trades = Trade.query.all()
+ for trade in trades:
+ trade.is_short = is_short
nb_trades = len(trades)
assert nb_trades == 1
assert trades[0].is_open is True
@@ -1868,26 +2015,42 @@ def test_handle_overlapping_signals(default_conf_usdt, ticker_usdt, limit_buy_or
patch_get_signal(freqtrade, enter_long=False)
assert freqtrade.handle_trade(trades[0]) is False
trades = Trade.query.all()
+ for trade in trades:
+ trade.is_short = is_short
nb_trades = len(trades)
assert nb_trades == 1
assert trades[0].is_open is True
# Buy and Sell are triggering, so doing nothing ...
- patch_get_signal(freqtrade, enter_long=True, exit_long=True)
+ if is_short:
+ patch_get_signal(freqtrade, enter_long=False, enter_short=True, exit_short=True)
+ else:
+ patch_get_signal(freqtrade, enter_long=True, exit_long=True)
assert freqtrade.handle_trade(trades[0]) is False
trades = Trade.query.all()
+ for trade in trades:
+ trade.is_short = is_short
nb_trades = len(trades)
assert nb_trades == 1
assert trades[0].is_open is True
# Sell is triggering, guess what : we are Selling!
- patch_get_signal(freqtrade, enter_long=False, exit_long=True)
+ if is_short:
+ patch_get_signal(freqtrade, enter_long=False, exit_short=True)
+ else:
+ patch_get_signal(freqtrade, enter_long=False, exit_long=True)
trades = Trade.query.all()
+ for trade in trades:
+ trade.is_short = is_short
assert freqtrade.handle_trade(trades[0]) is True
-def test_handle_trade_roi(default_conf_usdt, ticker_usdt, limit_buy_order_usdt_open,
- fee, mocker, caplog) -> None:
+@ pytest.mark.parametrize("is_short", [False, True])
+def test_handle_trade_roi(default_conf_usdt, ticker_usdt, limit_order_open, fee, mocker, caplog,
+ is_short) -> None:
+
+ open_order = limit_order_open[enter_side(is_short)]
+
caplog.set_level(logging.DEBUG)
patch_RPCManager(mocker)
@@ -1895,19 +2058,20 @@ def test_handle_trade_roi(default_conf_usdt, ticker_usdt, limit_buy_order_usdt_o
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt,
create_order=MagicMock(side_effect=[
- limit_buy_order_usdt_open,
+ open_order,
{'id': 1234553382},
]),
get_fee=fee,
)
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
- patch_get_signal(freqtrade)
+ patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
freqtrade.strategy.min_roi_reached = MagicMock(return_value=True)
freqtrade.enter_positions()
trade = Trade.query.first()
+ trade.is_short = is_short
trade.is_open = True
# FIX: sniffing logs, suggest handle_trade should not execute_trade_exit
@@ -1915,14 +2079,22 @@ def test_handle_trade_roi(default_conf_usdt, ticker_usdt, limit_buy_order_usdt_o
# we might just want to check if we are in a sell condition without
# executing
# if ROI is reached we must sell
+ # TODO-lev: Change the next line for shorts
+ caplog.clear()
patch_get_signal(freqtrade, enter_long=False, exit_long=True)
assert freqtrade.handle_trade(trade)
assert log_has("ETH/USDT - Required profit reached. sell_type=SellType.ROI",
caplog)
-def test_handle_trade_use_sell_signal(default_conf_usdt, ticker_usdt, limit_buy_order_usdt_open,
- limit_sell_order_usdt_open, fee, mocker, caplog) -> None:
+@ pytest.mark.parametrize("is_short", [False, True])
+def test_handle_trade_use_sell_signal(
+ default_conf_usdt, ticker_usdt, limit_order_open, fee, mocker, caplog, is_short
+) -> None:
+
+ enter_open_order = limit_order_open[exit_side(is_short)]
+ exit_open_order = limit_order_open[enter_side(is_short)]
+
# use_sell_signal is True buy default
caplog.set_level(logging.DEBUG)
patch_RPCManager(mocker)
@@ -1930,50 +2102,61 @@ def test_handle_trade_use_sell_signal(default_conf_usdt, ticker_usdt, limit_buy_
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt,
create_order=MagicMock(side_effect=[
- limit_buy_order_usdt_open,
- limit_sell_order_usdt_open,
+ enter_open_order,
+ exit_open_order,
]),
get_fee=fee,
)
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
- patch_get_signal(freqtrade)
+ patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
freqtrade.enter_positions()
trade = Trade.query.first()
+ trade.is_short = is_short
trade.is_open = True
patch_get_signal(freqtrade, enter_long=False, exit_long=False)
assert not freqtrade.handle_trade(trade)
- patch_get_signal(freqtrade, enter_long=False, exit_long=True)
+ if is_short:
+ patch_get_signal(freqtrade, enter_long=False, exit_short=True)
+ else:
+ patch_get_signal(freqtrade, enter_long=False, exit_long=True)
assert freqtrade.handle_trade(trade)
assert log_has("ETH/USDT - Sell signal received. sell_type=SellType.SELL_SIGNAL",
caplog)
-def test_close_trade(default_conf_usdt, ticker_usdt, limit_buy_order_usdt,
- limit_buy_order_usdt_open, limit_sell_order_usdt, fee, mocker) -> None:
+@ pytest.mark.parametrize("is_short", [False, True])
+def test_close_trade(
+ default_conf_usdt, ticker_usdt, limit_order_open,
+ limit_order, fee, mocker, is_short
+) -> None:
+ open_order = limit_order_open[exit_side(is_short)]
+ enter_order = limit_order[exit_side(is_short)]
+ exit_order = limit_order[enter_side(is_short)]
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt,
- create_order=MagicMock(return_value=limit_buy_order_usdt_open),
+ create_order=MagicMock(return_value=open_order),
get_fee=fee,
)
freqtrade = FreqtradeBot(default_conf_usdt)
- patch_get_signal(freqtrade)
+ patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
# Create trade and sell it
freqtrade.enter_positions()
trade = Trade.query.first()
+ trade.is_short = is_short
assert trade
- trade.update(limit_buy_order_usdt)
- trade.update(limit_sell_order_usdt)
+ trade.update(enter_order)
+ trade.update(exit_order)
assert trade.is_open is False
with pytest.raises(DependencyException, match=r'.*closed trade.*'):
@@ -1993,27 +2176,33 @@ def test_bot_loop_start_called_once(mocker, default_conf_usdt, caplog):
assert ftbot.strategy.analyze.call_count == 1
-def test_check_handle_timedout_buy_usercustom(default_conf_usdt, ticker_usdt, limit_buy_order_old,
- open_trade, fee, mocker) -> None:
- default_conf_usdt["unfilledtimeout"] = {"buy": 1400, "sell": 30}
+@ pytest.mark.parametrize("is_short", [False, True])
+def test_check_handle_timedout_buy_usercustom(
+ default_conf_usdt, ticker_usdt, limit_buy_order_old, open_trade,
+ limit_sell_order_old, fee, mocker, is_short
+) -> None:
+
+ old_order = limit_sell_order_old if is_short else limit_buy_order_old
+ default_conf_usdt["unfilledtimeout"] = {"buy": 30,
+ "sell": 1400} if is_short else {"buy": 1400, "sell": 30}
rpc_mock = patch_RPCManager(mocker)
- cancel_order_mock = MagicMock(return_value=limit_buy_order_old)
- cancel_buy_order = deepcopy(limit_buy_order_old)
- cancel_buy_order['status'] = 'canceled'
- cancel_order_wr_mock = MagicMock(return_value=cancel_buy_order)
+ cancel_order_mock = MagicMock(return_value=old_order)
+ cancel_enter_order = deepcopy(old_order)
+ cancel_enter_order['status'] = 'canceled'
+ cancel_order_wr_mock = MagicMock(return_value=cancel_enter_order)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt,
- fetch_order=MagicMock(return_value=limit_buy_order_old),
+ fetch_order=MagicMock(return_value=old_order),
cancel_order_with_result=cancel_order_wr_mock,
cancel_order=cancel_order_mock,
get_fee=fee
)
freqtrade = FreqtradeBot(default_conf_usdt)
-
+ open_trade.is_short = is_short
Trade.query.session.add(open_trade)
# Ensure default is to return empty (so not mocked yet)
@@ -2021,24 +2210,34 @@ def test_check_handle_timedout_buy_usercustom(default_conf_usdt, ticker_usdt, li
assert cancel_order_mock.call_count == 0
# Return false - trade remains open
- freqtrade.strategy.check_buy_timeout = MagicMock(return_value=False)
+ if is_short:
+ freqtrade.strategy.check_sell_timeout = MagicMock(return_value=False)
+ else:
+ freqtrade.strategy.check_buy_timeout = MagicMock(return_value=False)
freqtrade.check_handle_timedout()
assert cancel_order_mock.call_count == 0
trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
nb_trades = len(trades)
assert nb_trades == 1
- assert freqtrade.strategy.check_buy_timeout.call_count == 1
+ if is_short:
+ assert freqtrade.strategy.check_sell_timeout.call_count == 1
+ # Raise Keyerror ... (no impact on trade)
+ freqtrade.strategy.check_sell_timeout = MagicMock(side_effect=KeyError)
+ else:
+ assert freqtrade.strategy.check_buy_timeout.call_count == 1
+ freqtrade.strategy.check_buy_timeout = MagicMock(side_effect=KeyError)
- # Raise Keyerror ... (no impact on trade)
- freqtrade.strategy.check_buy_timeout = MagicMock(side_effect=KeyError)
freqtrade.check_handle_timedout()
assert cancel_order_mock.call_count == 0
trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
nb_trades = len(trades)
assert nb_trades == 1
- assert freqtrade.strategy.check_buy_timeout.call_count == 1
-
- freqtrade.strategy.check_buy_timeout = MagicMock(return_value=True)
+ if is_short:
+ assert freqtrade.strategy.check_sell_timeout.call_count == 1
+ freqtrade.strategy.check_sell_timeout = MagicMock(return_value=True)
+ else:
+ assert freqtrade.strategy.check_buy_timeout.call_count == 1
+ freqtrade.strategy.check_buy_timeout = MagicMock(return_value=True)
# Trade should be closed since the function returns true
freqtrade.check_handle_timedout()
assert cancel_order_wr_mock.call_count == 1
@@ -2046,28 +2245,39 @@ def test_check_handle_timedout_buy_usercustom(default_conf_usdt, ticker_usdt, li
trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
nb_trades = len(trades)
assert nb_trades == 0
- assert freqtrade.strategy.check_buy_timeout.call_count == 1
+ if is_short:
+ assert freqtrade.strategy.check_sell_timeout.call_count == 1
+ else:
+ assert freqtrade.strategy.check_buy_timeout.call_count == 1
-def test_check_handle_timedout_buy(default_conf_usdt, ticker_usdt, limit_buy_order_old, open_trade,
- fee, mocker) -> None:
+@ pytest.mark.parametrize("is_short", [False, True])
+def test_check_handle_timedout_buy(
+ default_conf_usdt, ticker_usdt, limit_buy_order_old, open_trade,
+ limit_sell_order_old, fee, mocker, is_short
+) -> None:
+ old_order = limit_sell_order_old if is_short else limit_buy_order_old
rpc_mock = patch_RPCManager(mocker)
- limit_buy_cancel = deepcopy(limit_buy_order_old)
+ limit_buy_cancel = deepcopy(old_order)
limit_buy_cancel['status'] = 'canceled'
cancel_order_mock = MagicMock(return_value=limit_buy_cancel)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt,
- fetch_order=MagicMock(return_value=limit_buy_order_old),
+ fetch_order=MagicMock(return_value=old_order),
cancel_order_with_result=cancel_order_mock,
get_fee=fee
)
freqtrade = FreqtradeBot(default_conf_usdt)
+ open_trade.is_short = is_short
Trade.query.session.add(open_trade)
- freqtrade.strategy.check_buy_timeout = MagicMock(return_value=False)
+ if is_short:
+ freqtrade.strategy.check_sell_timeout = MagicMock(return_value=False)
+ else:
+ freqtrade.strategy.check_buy_timeout = MagicMock(return_value=False)
# check it does cancel buy orders over the time limit
freqtrade.check_handle_timedout()
assert cancel_order_mock.call_count == 1
@@ -2076,25 +2286,32 @@ def test_check_handle_timedout_buy(default_conf_usdt, ticker_usdt, limit_buy_ord
nb_trades = len(trades)
assert nb_trades == 0
# Custom user buy-timeout is never called
- assert freqtrade.strategy.check_buy_timeout.call_count == 0
+ if is_short:
+ assert freqtrade.strategy.check_sell_timeout.call_count == 0
+ else:
+ assert freqtrade.strategy.check_buy_timeout.call_count == 0
-def test_check_handle_cancelled_buy(default_conf_usdt, ticker_usdt, limit_buy_order_old, open_trade,
- fee, mocker, caplog) -> None:
+@ pytest.mark.parametrize("is_short", [False, True])
+def test_check_handle_cancelled_buy(
+ default_conf_usdt, ticker_usdt, limit_buy_order_old, open_trade,
+ limit_sell_order_old, fee, mocker, caplog, is_short
+) -> None:
""" Handle Buy order cancelled on exchange"""
+ old_order = limit_sell_order_old if is_short else limit_buy_order_old
rpc_mock = patch_RPCManager(mocker)
cancel_order_mock = MagicMock()
patch_exchange(mocker)
- limit_buy_order_old.update({"status": "canceled", 'filled': 0.0})
+ old_order.update({"status": "canceled", 'filled': 0.0})
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt,
- fetch_order=MagicMock(return_value=limit_buy_order_old),
+ fetch_order=MagicMock(return_value=old_order),
cancel_order=cancel_order_mock,
get_fee=fee
)
freqtrade = FreqtradeBot(default_conf_usdt)
-
+ open_trade.is_short = is_short
Trade.query.session.add(open_trade)
# check it does cancel buy orders over the time limit
@@ -2104,11 +2321,15 @@ def test_check_handle_cancelled_buy(default_conf_usdt, ticker_usdt, limit_buy_or
trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
nb_trades = len(trades)
assert nb_trades == 0
- assert log_has_re("Buy order cancelled on exchange for Trade.*", caplog)
+ assert log_has_re(
+ f"{'Sell' if is_short else 'Buy'} order cancelled on exchange for Trade.*", caplog)
-def test_check_handle_timedout_buy_exception(default_conf_usdt, ticker_usdt,
- open_trade, fee, mocker) -> None:
+@ pytest.mark.parametrize("is_short", [False, True])
+def test_check_handle_timedout_buy_exception(
+ default_conf_usdt, ticker_usdt, limit_buy_order_old, open_trade,
+ is_short, fee, mocker
+) -> None:
rpc_mock = patch_RPCManager(mocker)
cancel_order_mock = MagicMock()
patch_exchange(mocker)
@@ -2133,8 +2354,11 @@ def test_check_handle_timedout_buy_exception(default_conf_usdt, ticker_usdt,
assert nb_trades == 1
-def test_check_handle_timedout_sell_usercustom(default_conf_usdt, ticker_usdt, limit_sell_order_old,
- mocker, open_trade) -> None:
+@ pytest.mark.parametrize("is_short", [False, True])
+def test_check_handle_timedout_sell_usercustom(
+ default_conf_usdt, ticker_usdt, limit_sell_order_old, mocker,
+ is_short, open_trade_usdt
+) -> None:
default_conf_usdt["unfilledtimeout"] = {"buy": 1440, "sell": 1440}
rpc_mock = patch_RPCManager(mocker)
cancel_order_mock = MagicMock()
@@ -2147,12 +2371,12 @@ def test_check_handle_timedout_sell_usercustom(default_conf_usdt, ticker_usdt, l
)
freqtrade = FreqtradeBot(default_conf_usdt)
- open_trade.open_date = arrow.utcnow().shift(hours=-5).datetime
- open_trade.close_date = arrow.utcnow().shift(minutes=-601).datetime
- open_trade.close_profit_abs = 0.001
- open_trade.is_open = False
+ open_trade_usdt.open_date = arrow.utcnow().shift(hours=-5).datetime
+ open_trade_usdt.close_date = arrow.utcnow().shift(minutes=-601).datetime
+ open_trade_usdt.close_profit_abs = 0.001
+ open_trade_usdt.is_open = False
- Trade.query.session.add(open_trade)
+ Trade.query.session.add(open_trade_usdt)
# Ensure default is false
freqtrade.check_handle_timedout()
assert cancel_order_mock.call_count == 0
@@ -2162,7 +2386,7 @@ def test_check_handle_timedout_sell_usercustom(default_conf_usdt, ticker_usdt, l
freqtrade.check_handle_timedout()
assert cancel_order_mock.call_count == 0
assert rpc_mock.call_count == 0
- assert open_trade.is_open is False
+ assert open_trade_usdt.is_open is False
assert freqtrade.strategy.check_sell_timeout.call_count == 1
freqtrade.strategy.check_sell_timeout = MagicMock(side_effect=KeyError)
@@ -2170,7 +2394,7 @@ def test_check_handle_timedout_sell_usercustom(default_conf_usdt, ticker_usdt, l
freqtrade.check_handle_timedout()
assert cancel_order_mock.call_count == 0
assert rpc_mock.call_count == 0
- assert open_trade.is_open is False
+ assert open_trade_usdt.is_open is False
assert freqtrade.strategy.check_sell_timeout.call_count == 1
# Return True - sells!
@@ -2178,12 +2402,15 @@ def test_check_handle_timedout_sell_usercustom(default_conf_usdt, ticker_usdt, l
freqtrade.check_handle_timedout()
assert cancel_order_mock.call_count == 1
assert rpc_mock.call_count == 1
- assert open_trade.is_open is True
+ assert open_trade_usdt.is_open is True
assert freqtrade.strategy.check_sell_timeout.call_count == 1
-def test_check_handle_timedout_sell(default_conf_usdt, ticker_usdt, limit_sell_order_old, mocker,
- open_trade) -> None:
+@ pytest.mark.parametrize("is_short", [False, True])
+def test_check_handle_timedout_sell(
+ default_conf_usdt, ticker_usdt, limit_sell_order_old,
+ mocker, is_short, open_trade_usdt
+) -> None:
rpc_mock = patch_RPCManager(mocker)
cancel_order_mock = MagicMock()
patch_exchange(mocker)
@@ -2195,25 +2422,28 @@ def test_check_handle_timedout_sell(default_conf_usdt, ticker_usdt, limit_sell_o
)
freqtrade = FreqtradeBot(default_conf_usdt)
- open_trade.open_date = arrow.utcnow().shift(hours=-5).datetime
- open_trade.close_date = arrow.utcnow().shift(minutes=-601).datetime
- open_trade.close_profit_abs = 0.001
- open_trade.is_open = False
+ open_trade_usdt.open_date = arrow.utcnow().shift(hours=-5).datetime
+ open_trade_usdt.close_date = arrow.utcnow().shift(minutes=-601).datetime
+ open_trade_usdt.close_profit_abs = 0.001
+ open_trade_usdt.is_open = False
- Trade.query.session.add(open_trade)
+ Trade.query.session.add(open_trade_usdt)
freqtrade.strategy.check_sell_timeout = MagicMock(return_value=False)
# check it does cancel sell orders over the time limit
freqtrade.check_handle_timedout()
assert cancel_order_mock.call_count == 1
assert rpc_mock.call_count == 1
- assert open_trade.is_open is True
+ assert open_trade_usdt.is_open is True
# Custom user sell-timeout is never called
assert freqtrade.strategy.check_sell_timeout.call_count == 0
-def test_check_handle_cancelled_sell(default_conf_usdt, ticker_usdt, limit_sell_order_old,
- open_trade, mocker, caplog) -> None:
+@ pytest.mark.parametrize("is_short", [False, True])
+def test_check_handle_cancelled_sell(
+ default_conf_usdt, ticker_usdt, limit_sell_order_old, open_trade_usdt,
+ is_short, mocker, caplog
+) -> None:
""" Handle sell order cancelled on exchange"""
rpc_mock = patch_RPCManager(mocker)
cancel_order_mock = MagicMock()
@@ -2227,22 +2457,25 @@ def test_check_handle_cancelled_sell(default_conf_usdt, ticker_usdt, limit_sell_
)
freqtrade = FreqtradeBot(default_conf_usdt)
- open_trade.open_date = arrow.utcnow().shift(hours=-5).datetime
- open_trade.close_date = arrow.utcnow().shift(minutes=-601).datetime
- open_trade.is_open = False
+ open_trade_usdt.open_date = arrow.utcnow().shift(hours=-5).datetime
+ open_trade_usdt.close_date = arrow.utcnow().shift(minutes=-601).datetime
+ open_trade_usdt.is_open = False
- Trade.query.session.add(open_trade)
+ Trade.query.session.add(open_trade_usdt)
# check it does cancel sell orders over the time limit
freqtrade.check_handle_timedout()
assert cancel_order_mock.call_count == 0
assert rpc_mock.call_count == 1
- assert open_trade.is_open is True
+ assert open_trade_usdt.is_open is True
assert log_has_re("Sell order cancelled on exchange for Trade.*", caplog)
-def test_check_handle_timedout_partial(default_conf_usdt, ticker_usdt, limit_buy_order_old_partial,
- open_trade, mocker) -> None:
+@ pytest.mark.parametrize("is_short", [False, True])
+def test_check_handle_timedout_partial(
+ default_conf_usdt, ticker_usdt, limit_buy_order_old_partial, is_short,
+ open_trade, mocker
+) -> None:
rpc_mock = patch_RPCManager(mocker)
limit_buy_canceled = deepcopy(limit_buy_order_old_partial)
limit_buy_canceled['status'] = 'canceled'
@@ -2270,9 +2503,12 @@ def test_check_handle_timedout_partial(default_conf_usdt, ticker_usdt, limit_buy
assert trades[0].stake_amount == open_trade.open_rate * trades[0].amount
-def test_check_handle_timedout_partial_fee(default_conf_usdt, ticker_usdt, open_trade, caplog, fee,
- limit_buy_order_old_partial, trades_for_order,
- limit_buy_order_old_partial_canceled, mocker) -> None:
+@ pytest.mark.parametrize("is_short", [False, True])
+def test_check_handle_timedout_partial_fee(
+ default_conf_usdt, ticker_usdt, open_trade, caplog, fee, is_short,
+ limit_buy_order_old_partial, trades_for_order,
+ limit_buy_order_old_partial_canceled, mocker
+) -> None:
rpc_mock = patch_RPCManager(mocker)
cancel_order_mock = MagicMock(return_value=limit_buy_order_old_partial_canceled)
mocker.patch('freqtrade.wallets.Wallets.get_free', MagicMock(return_value=0))
@@ -2309,9 +2545,12 @@ def test_check_handle_timedout_partial_fee(default_conf_usdt, ticker_usdt, open_
assert pytest.approx(trades[0].fee_open) == 0.001
-def test_check_handle_timedout_partial_except(default_conf_usdt, ticker_usdt, open_trade, caplog,
- fee, limit_buy_order_old_partial, trades_for_order,
- limit_buy_order_old_partial_canceled, mocker) -> None:
+@ pytest.mark.parametrize("is_short", [False, True])
+def test_check_handle_timedout_partial_except(
+ default_conf_usdt, ticker_usdt, open_trade, caplog, fee, is_short,
+ limit_buy_order_old_partial, trades_for_order,
+ limit_buy_order_old_partial_canceled, mocker
+) -> None:
rpc_mock = patch_RPCManager(mocker)
cancel_order_mock = MagicMock(return_value=limit_buy_order_old_partial_canceled)
patch_exchange(mocker)
@@ -2370,6 +2609,7 @@ def test_check_handle_timedout_exception(default_conf_usdt, ticker_usdt, open_tr
Trade.query.session.add(open_trade_usdt)
+ caplog.clear()
freqtrade.check_handle_timedout()
assert log_has_re(r"Cannot query order for Trade\(id=1, pair=ADA/USDT, amount=30.00000000, "
r"is_short=False, leverage=1.0, "
@@ -2379,10 +2619,13 @@ def test_check_handle_timedout_exception(default_conf_usdt, ticker_usdt, open_tr
caplog)
-def test_handle_cancel_enter(mocker, caplog, default_conf_usdt, limit_buy_order_usdt) -> None:
+@ pytest.mark.parametrize("is_short", [False, True])
+def test_handle_cancel_enter(mocker, caplog, default_conf_usdt, limit_order,
+ is_short) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
- cancel_buy_order = deepcopy(limit_buy_order_usdt)
+ l_order = limit_order[enter_side(is_short)]
+ cancel_buy_order = deepcopy(limit_order[enter_side(is_short)])
cancel_buy_order['status'] = 'canceled'
del cancel_buy_order['filled']
@@ -2395,36 +2638,39 @@ def test_handle_cancel_enter(mocker, caplog, default_conf_usdt, limit_buy_order_
trade = MagicMock()
trade.pair = 'LTC/USDT'
trade.open_rate = 200
- limit_buy_order_usdt['filled'] = 0.0
- limit_buy_order_usdt['status'] = 'open'
+ trade.is_short = False
+ trade.enter_side = "buy"
+ l_order['filled'] = 0.0
+ l_order['status'] = 'open'
reason = CANCEL_REASON['TIMEOUT']
- assert freqtrade.handle_cancel_enter(trade, limit_buy_order_usdt, reason)
+ assert freqtrade.handle_cancel_enter(trade, l_order, reason)
assert cancel_order_mock.call_count == 1
cancel_order_mock.reset_mock()
caplog.clear()
- limit_buy_order_usdt['filled'] = 0.01
- assert not freqtrade.handle_cancel_enter(trade, limit_buy_order_usdt, reason)
+ l_order['filled'] = 0.01
+ assert not freqtrade.handle_cancel_enter(trade, l_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.* unexitable.*", caplog)
caplog.clear()
cancel_order_mock.reset_mock()
- limit_buy_order_usdt['filled'] = 2
- assert not freqtrade.handle_cancel_enter(trade, limit_buy_order_usdt, reason)
+ l_order['filled'] = 2
+ assert not freqtrade.handle_cancel_enter(trade, l_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_enter(trade, limit_buy_order_usdt, reason)
+ assert not freqtrade.handle_cancel_enter(trade, l_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_enter_exchanges(mocker, caplog, default_conf_usdt,
+@ pytest.mark.parametrize("is_short", [False, True])
+@ pytest.mark.parametrize("limit_buy_order_canceled_empty", ['binance', 'ftx', 'kraken', 'bittrex'],
+ indirect=['limit_buy_order_canceled_empty'])
+def test_handle_cancel_enter_exchanges(mocker, caplog, default_conf_usdt, is_short,
limit_buy_order_canceled_empty) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
@@ -2437,22 +2683,25 @@ def test_handle_cancel_enter_exchanges(mocker, caplog, default_conf_usdt,
reason = CANCEL_REASON['TIMEOUT']
trade = MagicMock()
trade.pair = 'LTC/ETH'
+ trade.enter_side = "buy"
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
-@pytest.mark.parametrize('cancelorder', [
+@ pytest.mark.parametrize("is_short", [False, True])
+@ pytest.mark.parametrize('cancelorder', [
{},
{'remaining': None},
'String Return value',
123
])
-def test_handle_cancel_enter_corder_empty(mocker, default_conf_usdt, limit_buy_order_usdt,
+def test_handle_cancel_enter_corder_empty(mocker, default_conf_usdt, limit_order, is_short,
cancelorder) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
+ l_order = limit_order[enter_side(is_short)]
cancel_order_mock = MagicMock(return_value=cancelorder)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
@@ -2464,16 +2713,18 @@ def test_handle_cancel_enter_corder_empty(mocker, default_conf_usdt, limit_buy_o
trade = MagicMock()
trade.pair = 'LTC/USDT'
+ trade.enter_side = "buy"
trade.open_rate = 200
- limit_buy_order_usdt['filled'] = 0.0
- limit_buy_order_usdt['status'] = 'open'
+ trade.enter_side = "buy"
+ l_order['filled'] = 0.0
+ l_order['status'] = 'open'
reason = CANCEL_REASON['TIMEOUT']
- assert freqtrade.handle_cancel_enter(trade, limit_buy_order_usdt, reason)
+ assert freqtrade.handle_cancel_enter(trade, l_order, reason)
assert cancel_order_mock.call_count == 1
cancel_order_mock.reset_mock()
- limit_buy_order_usdt['filled'] = 1.0
- assert not freqtrade.handle_cancel_enter(trade, limit_buy_order_usdt, reason)
+ l_order['filled'] = 1.0
+ assert not freqtrade.handle_cancel_enter(trade, l_order, reason)
assert cancel_order_mock.call_count == 1
@@ -2538,8 +2789,12 @@ def test_handle_cancel_exit_cancel_exception(mocker, default_conf_usdt) -> None:
assert freqtrade.handle_cancel_exit(trade, order, reason) == 'error cancelling order'
-def test_execute_trade_exit_up(default_conf_usdt, ticker_usdt, fee, ticker_usdt_sell_up, mocker
- ) -> None:
+@ pytest.mark.parametrize("is_short, open_rate, amt", [
+ (False, 2.0, 30.0),
+ (True, 2.02, 29.70297029),
+])
+def test_execute_trade_exit_up(default_conf_usdt, ticker_usdt, fee, ticker_usdt_sell_up, mocker,
+ ticker_usdt_sell_down, is_short, open_rate, amt) -> None:
rpc_mock = patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
@@ -2550,7 +2805,7 @@ def test_execute_trade_exit_up(default_conf_usdt, ticker_usdt, fee, ticker_usdt_
)
patch_whitelist(mocker, default_conf_usdt)
freqtrade = FreqtradeBot(default_conf_usdt)
- patch_get_signal(freqtrade)
+ patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
freqtrade.strategy.confirm_trade_exit = MagicMock(return_value=False)
# Create some test data
@@ -2558,25 +2813,31 @@ def test_execute_trade_exit_up(default_conf_usdt, ticker_usdt, fee, ticker_usdt_
rpc_mock.reset_mock()
trade = Trade.query.first()
+ assert trade.is_short == is_short
assert trade
assert freqtrade.strategy.confirm_trade_exit.call_count == 0
# Increase the price and sell it
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
- fetch_ticker=ticker_usdt_sell_up
+ fetch_ticker=ticker_usdt_sell_down if is_short else ticker_usdt_sell_up
)
# Prevented sell ...
- freqtrade.execute_trade_exit(trade=trade, limit=ticker_usdt_sell_up()['bid'],
- sell_reason=SellCheckTuple(sell_type=SellType.ROI))
+ freqtrade.execute_trade_exit(
+ trade=trade,
+ limit=(ticker_usdt_sell_down()['ask'] if is_short else ticker_usdt_sell_up()['bid']),
+ sell_reason=SellCheckTuple(sell_type=SellType.ROI)
+ )
assert rpc_mock.call_count == 0
assert freqtrade.strategy.confirm_trade_exit.call_count == 1
# Repatch with true
freqtrade.strategy.confirm_trade_exit = MagicMock(return_value=True)
-
- freqtrade.execute_trade_exit(trade=trade, limit=ticker_usdt_sell_up()['bid'],
- sell_reason=SellCheckTuple(sell_type=SellType.ROI))
+ freqtrade.execute_trade_exit(
+ trade=trade,
+ limit=(ticker_usdt_sell_down()['ask'] if is_short else ticker_usdt_sell_up()['bid']),
+ sell_reason=SellCheckTuple(sell_type=SellType.ROI)
+ )
assert freqtrade.strategy.confirm_trade_exit.call_count == 1
assert rpc_mock.call_count == 1
@@ -2587,13 +2848,13 @@ def test_execute_trade_exit_up(default_conf_usdt, ticker_usdt, fee, ticker_usdt_
'exchange': 'Binance',
'pair': 'ETH/USDT',
'gain': 'profit',
- 'limit': 2.2,
- 'amount': 30.0,
+ 'limit': 2.0 if is_short else 2.2,
+ 'amount': amt,
'order_type': 'limit',
- 'open_rate': 2.0,
- 'current_rate': 2.3,
- 'profit_amount': 5.685,
- 'profit_ratio': 0.09451372,
+ 'open_rate': open_rate,
+ 'current_rate': 2.01 if is_short else 2.3,
+ 'profit_amount': 0.29554455 if is_short else 5.685,
+ 'profit_ratio': 0.00493809 if is_short else 0.09451372,
'stake_currency': 'USDT',
'fiat_currency': 'USD',
'sell_reason': SellType.ROI.value,
@@ -2603,8 +2864,9 @@ def test_execute_trade_exit_up(default_conf_usdt, ticker_usdt, fee, ticker_usdt_
} == last_msg
+@ pytest.mark.parametrize("is_short", [False, True])
def test_execute_trade_exit_down(default_conf_usdt, ticker_usdt, fee, ticker_usdt_sell_down,
- mocker) -> None:
+ ticker_usdt_sell_up, mocker, is_short) -> None:
rpc_mock = patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
@@ -2615,22 +2877,23 @@ def test_execute_trade_exit_down(default_conf_usdt, ticker_usdt, fee, ticker_usd
)
patch_whitelist(mocker, default_conf_usdt)
freqtrade = FreqtradeBot(default_conf_usdt)
- patch_get_signal(freqtrade)
+ patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
# Create some test data
freqtrade.enter_positions()
trade = Trade.query.first()
+ trade.is_short = is_short
assert trade
# Decrease the price and sell it
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
- fetch_ticker=ticker_usdt_sell_down
+ fetch_ticker=ticker_usdt_sell_up if is_short else ticker_usdt_sell_down
)
-
- freqtrade.execute_trade_exit(trade=trade, limit=ticker_usdt_sell_down()['bid'],
- sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
+ freqtrade.execute_trade_exit(
+ trade=trade, limit=(ticker_usdt_sell_up if is_short else ticker_usdt_sell_down)()['bid'],
+ sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
assert rpc_mock.call_count == 2
last_msg = rpc_mock.call_args_list[-1][0][0]
@@ -2640,13 +2903,13 @@ def test_execute_trade_exit_down(default_conf_usdt, ticker_usdt, fee, ticker_usd
'exchange': 'Binance',
'pair': 'ETH/USDT',
'gain': 'loss',
- 'limit': 2.01,
- 'amount': 30.0,
+ 'limit': 2.2 if is_short else 2.01,
+ 'amount': 29.70297029 if is_short else 30.0,
'order_type': 'limit',
- 'open_rate': 2.0,
- 'current_rate': 2.0,
- 'profit_amount': -0.00075,
- 'profit_ratio': -1.247e-05,
+ 'open_rate': 2.02 if is_short else 2.0,
+ 'current_rate': 2.2 if is_short else 2.0,
+ 'profit_amount': -5.65990099 if is_short else -0.00075,
+ 'profit_ratio': -0.0945681 if is_short else -1.247e-05,
'stake_currency': 'USDT',
'fiat_currency': 'USD',
'sell_reason': SellType.STOP_LOSS.value,
@@ -2656,8 +2919,14 @@ def test_execute_trade_exit_down(default_conf_usdt, ticker_usdt, fee, ticker_usd
} == last_msg
-def test_execute_trade_exit_custom_exit_price(default_conf_usdt, ticker_usdt, fee,
- ticker_usdt_sell_up, mocker) -> None:
+@pytest.mark.parametrize(
+ "is_short,amount,open_rate,current_rate,limit,profit_amount,profit_ratio,profit_or_loss", [
+ (False, 30, 2.0, 2.3, 2.25, 7.18125, 0.11938903, 'profit'),
+ (True, 29.70297029, 2.02, 2.2, 2.25, -7.14876237, -0.11944465, 'loss'), # TODO-lev
+ ])
+def test_execute_trade_exit_custom_exit_price(
+ default_conf_usdt, ticker_usdt, fee, ticker_usdt_sell_up, is_short, amount, open_rate,
+ current_rate, limit, profit_amount, profit_ratio, profit_or_loss, mocker) -> None:
rpc_mock = patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
@@ -2670,7 +2939,7 @@ def test_execute_trade_exit_custom_exit_price(default_conf_usdt, ticker_usdt, fe
config['custom_price_max_distance_ratio'] = 0.1
patch_whitelist(mocker, config)
freqtrade = FreqtradeBot(config)
- patch_get_signal(freqtrade)
+ patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
freqtrade.strategy.confirm_trade_exit = MagicMock(return_value=False)
# Create some test data
@@ -2678,6 +2947,7 @@ def test_execute_trade_exit_custom_exit_price(default_conf_usdt, ticker_usdt, fe
rpc_mock.reset_mock()
trade = Trade.query.first()
+ trade.is_short = is_short
assert trade
assert freqtrade.strategy.confirm_trade_exit.call_count == 0
@@ -2691,7 +2961,7 @@ def test_execute_trade_exit_custom_exit_price(default_conf_usdt, ticker_usdt, fe
# Set a custom exit price
freqtrade.strategy.custom_exit_price = lambda **kwargs: 2.25
-
+ # TODO-lev: side="buy"
freqtrade.execute_trade_exit(trade=trade, limit=ticker_usdt_sell_up()['bid'],
sell_reason=SellCheckTuple(sell_type=SellType.SELL_SIGNAL))
@@ -2706,14 +2976,14 @@ def test_execute_trade_exit_custom_exit_price(default_conf_usdt, ticker_usdt, fe
'type': RPCMessageType.SELL,
'exchange': 'Binance',
'pair': 'ETH/USDT',
- 'gain': 'profit',
- 'limit': 2.25,
- 'amount': 30.0,
+ 'gain': profit_or_loss,
+ 'limit': limit,
+ 'amount': amount,
'order_type': 'limit',
- 'open_rate': 2.0,
- 'current_rate': 2.3,
- 'profit_amount': 7.18125,
- 'profit_ratio': 0.11938903,
+ 'open_rate': open_rate,
+ 'current_rate': current_rate,
+ 'profit_amount': profit_amount,
+ 'profit_ratio': profit_ratio,
'stake_currency': 'USDT',
'fiat_currency': 'USD',
'sell_reason': SellType.SELL_SIGNAL.value,
@@ -2723,8 +2993,10 @@ def test_execute_trade_exit_custom_exit_price(default_conf_usdt, ticker_usdt, fe
} == last_msg
+@ pytest.mark.parametrize("is_short", [False, True])
def test_execute_trade_exit_down_stoploss_on_exchange_dry_run(
- default_conf_usdt, ticker_usdt, fee, ticker_usdt_sell_down, mocker) -> None:
+ default_conf_usdt, ticker_usdt, fee, is_short, ticker_usdt_sell_down,
+ ticker_usdt_sell_up, mocker) -> None:
rpc_mock = patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
@@ -2735,27 +3007,29 @@ def test_execute_trade_exit_down_stoploss_on_exchange_dry_run(
)
patch_whitelist(mocker, default_conf_usdt)
freqtrade = FreqtradeBot(default_conf_usdt)
- patch_get_signal(freqtrade)
+ patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
# Create some test data
freqtrade.enter_positions()
trade = Trade.query.first()
+ assert trade.is_short == is_short
assert trade
# Decrease the price and sell it
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
- fetch_ticker=ticker_usdt_sell_down
+ fetch_ticker=ticker_usdt_sell_up if is_short else ticker_usdt_sell_down
)
default_conf_usdt['dry_run'] = True
freqtrade.strategy.order_types['stoploss_on_exchange'] = True
# Setting trade stoploss to 0.01
- trade.stop_loss = 2.0 * 0.99
- freqtrade.execute_trade_exit(trade=trade, limit=ticker_usdt_sell_down()['bid'],
- sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
+ trade.stop_loss = 2.0 * 1.01 if is_short else 2.0 * 0.99
+ freqtrade.execute_trade_exit(
+ trade=trade, limit=(ticker_usdt_sell_up if is_short else ticker_usdt_sell_down())['bid'],
+ sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
assert rpc_mock.call_count == 2
last_msg = rpc_mock.call_args_list[-1][0][0]
@@ -2766,13 +3040,13 @@ def test_execute_trade_exit_down_stoploss_on_exchange_dry_run(
'exchange': 'Binance',
'pair': 'ETH/USDT',
'gain': 'loss',
- 'limit': 1.98,
- 'amount': 30.0,
+ 'limit': 2.02 if is_short else 1.98,
+ 'amount': 29.70297029 if is_short else 30.0,
'order_type': 'limit',
- 'open_rate': 2.0,
- 'current_rate': 2.0,
- 'profit_amount': -0.8985,
- 'profit_ratio': -0.01493766,
+ 'open_rate': 2.02 if is_short else 2.0,
+ 'current_rate': 2.2 if is_short else 2.0,
+ 'profit_amount': -0.3 if is_short else -0.8985,
+ 'profit_ratio': -0.00501253 if is_short else -0.01493766,
'stake_currency': 'USDT',
'fiat_currency': 'USD',
'sell_reason': SellType.STOP_LOSS.value,
@@ -2810,14 +3084,16 @@ def test_execute_trade_exit_sloe_cancel_exception(
freqtrade.config['dry_run'] = False
trade.stoploss_order_id = "abcd"
+ # TODO-lev: side="buy"
freqtrade.execute_trade_exit(trade=trade, limit=1234,
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
assert create_order_mock.call_count == 2
assert log_has('Could not cancel stoploss order abcd', caplog)
-def test_execute_trade_exit_with_stoploss_on_exchange(default_conf_usdt, ticker_usdt, fee,
- ticker_usdt_sell_up, mocker) -> None:
+@ pytest.mark.parametrize("is_short", [False, True])
+def test_execute_trade_exit_with_stoploss_on_exchange(
+ default_conf_usdt, ticker_usdt, fee, ticker_usdt_sell_up, is_short, mocker) -> None:
default_conf_usdt['exchange']['name'] = 'binance'
rpc_mock = patch_RPCManager(mocker)
@@ -2843,12 +3119,13 @@ def test_execute_trade_exit_with_stoploss_on_exchange(default_conf_usdt, ticker_
freqtrade = FreqtradeBot(default_conf_usdt)
freqtrade.strategy.order_types['stoploss_on_exchange'] = True
- patch_get_signal(freqtrade)
+ patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
# Create some test data
freqtrade.enter_positions()
trade = Trade.query.first()
+ trade.is_short = is_short
assert trade
trades = [trade]
@@ -2861,10 +3138,12 @@ def test_execute_trade_exit_with_stoploss_on_exchange(default_conf_usdt, ticker_
fetch_ticker=ticker_usdt_sell_up
)
+ # TODO-lev: side="buy"
freqtrade.execute_trade_exit(trade=trade, limit=ticker_usdt_sell_up()['bid'],
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
trade = Trade.query.first()
+ trade.is_short = is_short
assert trade
assert cancel_order.call_count == 1
assert rpc_mock.call_count == 3
@@ -2942,8 +3221,33 @@ def test_may_execute_trade_exit_after_stoploss_on_exchange_hit(default_conf_usdt
assert rpc_mock.call_args_list[2][0][0]['type'] == RPCMessageType.SELL
-def test_execute_trade_exit_market_order(default_conf_usdt, ticker_usdt, fee,
- ticker_usdt_sell_up, mocker) -> None:
+@pytest.mark.parametrize(
+ "is_short,amount,open_rate,current_rate,limit,profit_amount,profit_ratio,profit_or_loss", [
+ (False, 30, 2.0, 2.3, 2.2, 5.685, 0.09451372, 'profit'),
+ # TODO-lev: Should the current rate be 2.2 for shorts?
+ (True, 29.70297029, 2.02, 2.2, 2.3, -8.63762376, -0.1443212, 'loss'),
+ ])
+def test_execute_trade_exit_market_order(
+ default_conf_usdt, ticker_usdt, fee, is_short, current_rate, amount, open_rate,
+ limit, profit_amount, profit_ratio, profit_or_loss, ticker_usdt_sell_up, mocker
+) -> None:
+ """
+ amount
+ long: 60 / 2.0 = 30
+ short: 60 / 2.02 = 29.70297029
+ open_value
+ long: (30 * 2.0) + (30 * 2.0 * 0.0025) = 60.15
+ short: (29.702970297029704 * 2.02) - (29.702970297029704 * 2.02 * 0.0025) = 59.85
+ close_value
+ long: (30 * 2.2) - (30 * 2.2 * 0.0025) = 65.835
+ short: (29.702970297029704 * 2.3) + (29.702970297029704 * 2.3 * 0.0025) = 68.48762376237624
+ profit
+ long: 65.835 - 60.15 = 5.684999999999995
+ short: 59.85 - 68.48762376237624 = -8.637623762376244
+ profit_ratio
+ long: (65.835/60.15) - 1 = 0.0945137157107232
+ short: 1 - (68.48762376237624/59.85) = -0.1443211990371971
+ """
rpc_mock = patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
@@ -2954,12 +3258,13 @@ def test_execute_trade_exit_market_order(default_conf_usdt, ticker_usdt, fee,
)
patch_whitelist(mocker, default_conf_usdt)
freqtrade = FreqtradeBot(default_conf_usdt)
- patch_get_signal(freqtrade)
+ patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
# Create some test data
freqtrade.enter_positions()
trade = Trade.query.first()
+ trade.is_short = is_short
assert trade
# Increase the price and sell it
@@ -2969,11 +3274,15 @@ def test_execute_trade_exit_market_order(default_conf_usdt, ticker_usdt, fee,
)
freqtrade.config['order_types']['sell'] = 'market'
- freqtrade.execute_trade_exit(trade=trade, limit=ticker_usdt_sell_up()['bid'],
- sell_reason=SellCheckTuple(sell_type=SellType.ROI))
+ # TODO-lev: side="buy"
+ freqtrade.execute_trade_exit(
+ trade=trade,
+ limit=ticker_usdt_sell_up()['ask' if is_short else 'bid'],
+ sell_reason=SellCheckTuple(sell_type=SellType.ROI)
+ )
assert not trade.is_open
- assert trade.close_profit == 0.09451372
+ assert trade.close_profit == profit_ratio
assert rpc_mock.call_count == 3
last_msg = rpc_mock.call_args_list[-1][0][0]
@@ -2982,14 +3291,14 @@ def test_execute_trade_exit_market_order(default_conf_usdt, ticker_usdt, fee,
'trade_id': 1,
'exchange': 'Binance',
'pair': 'ETH/USDT',
- 'gain': 'profit',
- 'limit': 2.2,
- 'amount': 30.0,
+ 'gain': profit_or_loss,
+ 'limit': limit,
+ 'amount': round(amount, 9),
'order_type': 'market',
- 'open_rate': 2.0,
- 'current_rate': 2.3,
- 'profit_amount': 5.685,
- 'profit_ratio': 0.09451372,
+ 'open_rate': open_rate,
+ 'current_rate': current_rate,
+ 'profit_amount': profit_amount,
+ 'profit_ratio': profit_ratio,
'stake_currency': 'USDT',
'fiat_currency': 'USD',
'sell_reason': SellType.ROI.value,
@@ -3000,7 +3309,8 @@ def test_execute_trade_exit_market_order(default_conf_usdt, ticker_usdt, fee,
} == last_msg
-def test_execute_trade_exit_insufficient_funds_error(default_conf_usdt, ticker_usdt, fee,
+@ pytest.mark.parametrize("is_short", [False, True])
+def test_execute_trade_exit_insufficient_funds_error(default_conf_usdt, ticker_usdt, fee, is_short,
ticker_usdt_sell_up, mocker) -> None:
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
mock_insuf = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_insufficient_funds')
@@ -3013,12 +3323,13 @@ def test_execute_trade_exit_insufficient_funds_error(default_conf_usdt, ticker_u
InsufficientFundsError(),
]),
)
- patch_get_signal(freqtrade)
+ patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
# Create some test data
freqtrade.enter_positions()
trade = Trade.query.first()
+ trade.is_short = is_short
assert trade
# Increase the price and sell it
@@ -3028,24 +3339,29 @@ def test_execute_trade_exit_insufficient_funds_error(default_conf_usdt, ticker_u
)
sell_reason = SellCheckTuple(sell_type=SellType.ROI)
+ # TODO-lev: side="buy"
assert not freqtrade.execute_trade_exit(trade=trade, limit=ticker_usdt_sell_up()['bid'],
sell_reason=sell_reason)
assert mock_insuf.call_count == 1
-@pytest.mark.parametrize('profit_only,bid,ask,handle_first,handle_second,sell_type', [
+@ pytest.mark.parametrize('profit_only,bid,ask,handle_first,handle_second,sell_type,is_short', [
# Enable profit
- (True, 1.9, 2.2, False, True, SellType.SELL_SIGNAL.value),
- # Disable profit
- (False, 2.9, 3.2, True, False, SellType.SELL_SIGNAL.value),
- # Enable loss
- # * Shouldn't this be SellType.STOP_LOSS.value
- (True, 0.19, 0.22, False, False, None),
+ (True, 2.18, 2.2, False, True, SellType.SELL_SIGNAL.value, False),
+ (True, 2.18, 2.2, False, True, SellType.SELL_SIGNAL.value, True),
+ # # Disable profit
+ (False, 3.19, 3.2, True, False, SellType.SELL_SIGNAL.value, False),
+ (False, 3.19, 3.2, True, False, SellType.SELL_SIGNAL.value, True),
+ # # Enable loss
+ # # * Shouldn't this be SellType.STOP_LOSS.value
+ (True, 0.21, 0.22, False, False, None, False),
+ (True, 2.41, 2.42, False, False, None, True),
# Disable loss
- (False, 0.10, 0.22, True, False, SellType.SELL_SIGNAL.value),
+ (False, 0.10, 0.22, True, False, SellType.SELL_SIGNAL.value, False),
+ (False, 0.10, 0.22, True, False, SellType.SELL_SIGNAL.value, True),
])
def test_sell_profit_only(
- default_conf_usdt, limit_buy_order_usdt, limit_buy_order_usdt_open,
+ default_conf_usdt, limit_order, limit_order_open, is_short,
fee, mocker, profit_only, bid, ask, handle_first, handle_second, sell_type) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
@@ -3057,7 +3373,7 @@ def test_sell_profit_only(
'last': bid
}),
create_order=MagicMock(side_effect=[
- limit_buy_order_usdt_open,
+ limit_order_open[enter_side(is_short)],
{'id': 1234553382},
]),
get_fee=fee,
@@ -3068,7 +3384,7 @@ def test_sell_profit_only(
'sell_profit_offset': 0.1,
})
freqtrade = FreqtradeBot(default_conf_usdt)
- patch_get_signal(freqtrade)
+ patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
if sell_type == SellType.SELL_SIGNAL.value:
freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
else:
@@ -3077,9 +3393,10 @@ def test_sell_profit_only(
freqtrade.enter_positions()
trade = Trade.query.first()
- trade.update(limit_buy_order_usdt)
+ trade.is_short = is_short
+ trade.update(limit_order[enter_side(is_short)])
freqtrade.wallets.update()
- patch_get_signal(freqtrade, enter_long=False, exit_long=True)
+ patch_get_signal(freqtrade, enter_long=False, exit_short=is_short, exit_long=not is_short)
assert freqtrade.handle_trade(trade) is handle_first
if handle_second:
@@ -3087,7 +3404,7 @@ def test_sell_profit_only(
assert freqtrade.handle_trade(trade) is True
-def test_sell_not_enough_balance(default_conf_usdt, limit_buy_order_usdt, limit_buy_order_usdt_open,
+def test_sell_not_enough_balance(default_conf_usdt, limit_order, limit_order_open,
fee, mocker, caplog) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
@@ -3099,7 +3416,7 @@ def test_sell_not_enough_balance(default_conf_usdt, limit_buy_order_usdt, limit_
'last': 0.00002172
}),
create_order=MagicMock(side_effect=[
- limit_buy_order_usdt_open,
+ limit_order_open['buy'],
{'id': 1234553382},
]),
get_fee=fee,
@@ -3113,7 +3430,7 @@ def test_sell_not_enough_balance(default_conf_usdt, limit_buy_order_usdt, limit_
trade = Trade.query.first()
amnt = trade.amount
- trade.update(limit_buy_order_usdt)
+ trade.update(limit_order['buy'])
patch_get_signal(freqtrade, enter_long=False, exit_long=True)
mocker.patch('freqtrade.wallets.Wallets.get_free', MagicMock(return_value=trade.amount * 0.985))
@@ -3122,7 +3439,7 @@ def test_sell_not_enough_balance(default_conf_usdt, limit_buy_order_usdt, limit_
assert trade.amount != amnt
-@pytest.mark.parametrize('amount_wallet,has_err', [
+@ pytest.mark.parametrize('amount_wallet,has_err', [
(95.29, False),
(91.29, True)
])
@@ -3159,8 +3476,9 @@ def test__safe_exit_amount(default_conf_usdt, fee, caplog, mocker, amount_wallet
assert wallet_update.call_count == 1
-def test_locked_pairs(default_conf_usdt, ticker_usdt, fee, ticker_usdt_sell_down, mocker,
- caplog) -> None:
+@ pytest.mark.parametrize("is_short", [False, True])
+def test_locked_pairs(default_conf_usdt, ticker_usdt, fee,
+ ticker_usdt_sell_down, mocker, caplog, is_short) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
@@ -3169,12 +3487,13 @@ def test_locked_pairs(default_conf_usdt, ticker_usdt, fee, ticker_usdt_sell_down
get_fee=fee,
)
freqtrade = FreqtradeBot(default_conf_usdt)
- patch_get_signal(freqtrade)
+ patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
# Create some test data
freqtrade.enter_positions()
trade = Trade.query.first()
+ trade.is_short = is_short
assert trade
# Decrease the price and sell it
@@ -3183,6 +3502,7 @@ def test_locked_pairs(default_conf_usdt, ticker_usdt, fee, ticker_usdt_sell_down
fetch_ticker=ticker_usdt_sell_down
)
+ # TODO-lev: side="buy"
freqtrade.execute_trade_exit(trade=trade, limit=ticker_usdt_sell_down()['bid'],
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
trade.close(ticker_usdt_sell_down()['bid'])
@@ -3195,8 +3515,9 @@ def test_locked_pairs(default_conf_usdt, ticker_usdt, fee, ticker_usdt_sell_down
assert log_has_re(f"Pair {trade.pair} is still locked.*", caplog)
-def test_ignore_roi_if_buy_signal(default_conf_usdt, limit_buy_order_usdt,
- limit_buy_order_usdt_open, fee, mocker) -> None:
+@ pytest.mark.parametrize("is_short", [False, True])
+def test_ignore_roi_if_buy_signal(default_conf_usdt, limit_order, limit_order_open, is_short,
+ fee, mocker) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
@@ -3207,7 +3528,7 @@ def test_ignore_roi_if_buy_signal(default_conf_usdt, limit_buy_order_usdt,
'last': 2.19
}),
create_order=MagicMock(side_effect=[
- limit_buy_order_usdt_open,
+ limit_order_open[enter_side(is_short)],
{'id': 1234553382},
]),
get_fee=fee,
@@ -3215,25 +3536,37 @@ def test_ignore_roi_if_buy_signal(default_conf_usdt, limit_buy_order_usdt,
default_conf_usdt['ignore_roi_if_buy_signal'] = True
freqtrade = FreqtradeBot(default_conf_usdt)
- patch_get_signal(freqtrade)
+ patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
freqtrade.strategy.min_roi_reached = MagicMock(return_value=True)
freqtrade.enter_positions()
trade = Trade.query.first()
- trade.update(limit_buy_order_usdt)
+ trade.is_short = is_short
+ trade.update(limit_order[enter_side(is_short)])
freqtrade.wallets.update()
- patch_get_signal(freqtrade, enter_long=True, exit_long=True)
+ if is_short:
+ patch_get_signal(freqtrade, enter_long=False, enter_short=True, exit_short=True)
+ else:
+ patch_get_signal(freqtrade, enter_long=True, exit_long=True)
+
assert freqtrade.handle_trade(trade) is False
# Test if buy-signal is absent (should sell due to roi = true)
- patch_get_signal(freqtrade, enter_long=False, exit_long=True)
+ if is_short:
+ patch_get_signal(freqtrade, enter_long=False, exit_short=True)
+ else:
+ patch_get_signal(freqtrade, enter_long=False, exit_long=True)
assert freqtrade.handle_trade(trade) is True
assert trade.sell_reason == SellType.ROI.value
-def test_trailing_stop_loss(default_conf_usdt, limit_buy_order_usdt_open,
- fee, caplog, mocker) -> None:
+@ pytest.mark.parametrize("is_short,val1,val2", [
+ (False, 1.5, 1.1),
+ (True, 0.5, 0.9)
+])
+def test_trailing_stop_loss(default_conf_usdt, limit_order_open,
+ is_short, val1, val2, fee, caplog, mocker) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
@@ -3244,7 +3577,7 @@ def test_trailing_stop_loss(default_conf_usdt, limit_buy_order_usdt_open,
'last': 2.0
}),
create_order=MagicMock(side_effect=[
- limit_buy_order_usdt_open,
+ limit_order_open[enter_side(is_short)],
{'id': 1234553382},
]),
get_fee=fee,
@@ -3252,19 +3585,20 @@ def test_trailing_stop_loss(default_conf_usdt, limit_buy_order_usdt_open,
default_conf_usdt['trailing_stop'] = True
patch_whitelist(mocker, default_conf_usdt)
freqtrade = FreqtradeBot(default_conf_usdt)
- patch_get_signal(freqtrade)
+ patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
freqtrade.enter_positions()
trade = Trade.query.first()
+ assert trade.is_short == is_short
assert freqtrade.handle_trade(trade) is False
- # Raise ticker_usdt above buy price
+ # Raise praise into profits
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
MagicMock(return_value={
- 'bid': 2.0 * 1.5,
- 'ask': 2.0 * 1.5,
- 'last': 2.0 * 1.5
+ 'bid': 2.0 * val1,
+ 'ask': 2.0 * val1,
+ 'last': 2.0 * val1
}))
# Stoploss should be adjusted
@@ -3273,40 +3607,46 @@ def test_trailing_stop_loss(default_conf_usdt, limit_buy_order_usdt_open,
# Price fell
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
MagicMock(return_value={
- 'bid': 2.0 * 1.1,
- 'ask': 2.0 * 1.1,
- 'last': 2.0 * 1.1
+ 'bid': 2.0 * val2,
+ 'ask': 2.0 * val2,
+ 'last': 2.0 * val2
}))
caplog.set_level(logging.DEBUG)
# Sell as trailing-stop is reached
assert freqtrade.handle_trade(trade) is True
- assert log_has("ETH/USDT - HIT STOP: current price at 2.200000, stoploss is 2.700000, "
- "initial stoploss was at 1.800000, trade opened at 2.000000", caplog)
+ stop_multi = 1.1 if is_short else 0.9
+ assert log_has(f"ETH/USDT - HIT STOP: current price at {(2.0 * val2):6f}, "
+ f"stoploss is {(2.0 * val1 * stop_multi):6f}, "
+ f"initial stoploss was at {(2.0 * stop_multi):6f}, trade opened at 2.000000",
+ caplog)
assert trade.sell_reason == SellType.TRAILING_STOP_LOSS.value
-@pytest.mark.parametrize('offset,trail_if_reached,second_sl', [
- (0, False, 2.0394),
- (0.011, False, 2.0394),
- (0.055, True, 1.8),
+@ pytest.mark.parametrize('offset,trail_if_reached,second_sl,is_short', [
+ (0, False, 2.0394, False),
+ (0.011, False, 2.0394, False),
+ (0.055, True, 1.8, False),
+ (0, False, 2.1614, True),
+ (0.011, False, 2.1614, True),
+ (0.055, True, 2.42, True),
])
def test_trailing_stop_loss_positive(
- default_conf_usdt, limit_buy_order_usdt, limit_buy_order_usdt_open,
- offset, fee, caplog, mocker, trail_if_reached, second_sl
+ default_conf_usdt, limit_order, limit_order_open,
+ offset, fee, caplog, mocker, trail_if_reached, second_sl, is_short
) -> None:
- buy_price = limit_buy_order_usdt['price']
+ enter_price = limit_order[enter_side(is_short)]['price']
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=MagicMock(return_value={
- 'bid': buy_price - 0.01,
- 'ask': buy_price - 0.01,
- 'last': buy_price - 0.01
+ 'bid': enter_price - (-0.01 if is_short else 0.01),
+ 'ask': enter_price - (-0.01 if is_short else 0.01),
+ 'last': enter_price - (-0.01 if is_short else 0.01),
}),
create_order=MagicMock(side_effect=[
- limit_buy_order_usdt_open,
+ limit_order_open[enter_side(is_short)],
{'id': 1234553382},
]),
get_fee=fee,
@@ -3319,12 +3659,13 @@ def test_trailing_stop_loss_positive(
patch_whitelist(mocker, default_conf_usdt)
freqtrade = FreqtradeBot(default_conf_usdt)
- patch_get_signal(freqtrade)
+ patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
freqtrade.enter_positions()
trade = Trade.query.first()
- trade.update(limit_buy_order_usdt)
+ trade.is_short = is_short
+ trade.update(limit_order[enter_side(is_short)])
caplog.set_level(logging.DEBUG)
# stop-loss not reached
assert freqtrade.handle_trade(trade) is False
@@ -3333,34 +3674,36 @@ def test_trailing_stop_loss_positive(
mocker.patch(
'freqtrade.exchange.Exchange.fetch_ticker',
MagicMock(return_value={
- 'bid': buy_price + 0.06,
- 'ask': buy_price + 0.06,
- 'last': buy_price + 0.06
+ 'bid': enter_price + (-0.06 if is_short else 0.06),
+ 'ask': enter_price + (-0.06 if is_short else 0.06),
+ 'last': enter_price + (-0.06 if is_short else 0.06),
})
)
# stop-loss not reached, adjusted stoploss
assert freqtrade.handle_trade(trade) is False
- caplog_text = f"ETH/USDT - Using positive stoploss: 0.01 offset: {offset} profit: 0.0249%"
+ caplog_text = (f"ETH/USDT - Using positive stoploss: 0.01 offset: {offset} profit: "
+ f"{'0.0249' if not is_short else '0.0224'}%")
if trail_if_reached:
assert not log_has(caplog_text, caplog)
assert not log_has("ETH/USDT - Adjusting stoploss...", caplog)
else:
assert log_has(caplog_text, caplog)
assert log_has("ETH/USDT - Adjusting stoploss...", caplog)
- assert trade.stop_loss == second_sl
+ assert pytest.approx(trade.stop_loss) == second_sl
caplog.clear()
mocker.patch(
'freqtrade.exchange.Exchange.fetch_ticker',
MagicMock(return_value={
- 'bid': buy_price + 0.125,
- 'ask': buy_price + 0.125,
- 'last': buy_price + 0.125,
+ 'bid': enter_price + (-0.135 if is_short else 0.125),
+ 'ask': enter_price + (-0.135 if is_short else 0.125),
+ 'last': enter_price + (-0.135 if is_short else 0.125),
})
)
assert freqtrade.handle_trade(trade) is False
assert log_has(
- f"ETH/USDT - Using positive stoploss: 0.01 offset: {offset} profit: 0.0572%",
+ f"ETH/USDT - Using positive stoploss: 0.01 offset: {offset} profit: "
+ f"{'0.0572' if not is_short else '0.0567'}%",
caplog
)
assert log_has("ETH/USDT - Adjusting stoploss...", caplog)
@@ -3368,22 +3711,25 @@ def test_trailing_stop_loss_positive(
mocker.patch(
'freqtrade.exchange.Exchange.fetch_ticker',
MagicMock(return_value={
- 'bid': buy_price + 0.02,
- 'ask': buy_price + 0.02,
- 'last': buy_price + 0.02
+ 'bid': enter_price + (-0.02 if is_short else 0.02),
+ 'ask': enter_price + (-0.02 if is_short else 0.02),
+ 'last': enter_price + (-0.02 if is_short else 0.02),
})
)
# Lower price again (but still positive)
assert freqtrade.handle_trade(trade) is True
assert log_has(
- f"ETH/USDT - HIT STOP: current price at {buy_price + 0.02:.6f}, "
+ f"ETH/USDT - HIT STOP: current price at {enter_price + (-0.02 if is_short else 0.02):.6f}, "
f"stoploss is {trade.stop_loss:.6f}, "
- f"initial stoploss was at 1.800000, trade opened at 2.000000", caplog)
+ f"initial stoploss was at {'2.42' if is_short else '1.80'}0000, "
+ f"trade opened at {2.2 if is_short else 2.0}00000",
+ caplog)
assert trade.sell_reason == SellType.TRAILING_STOP_LOSS.value
-def test_disable_ignore_roi_if_buy_signal(default_conf_usdt, limit_buy_order_usdt,
- limit_buy_order_usdt_open, fee, mocker) -> None:
+@pytest.mark.parametrize("is_short", [False, True])
+def test_disable_ignore_roi_if_buy_signal(default_conf_usdt, limit_order, limit_order_open,
+ is_short, fee, mocker) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
@@ -3394,7 +3740,7 @@ def test_disable_ignore_roi_if_buy_signal(default_conf_usdt, limit_buy_order_usd
'last': 2.0
}),
create_order=MagicMock(side_effect=[
- limit_buy_order_usdt_open,
+ limit_order_open[enter_side(is_short)],
{'id': 1234553382},
{'id': 1234553383}
]),
@@ -3405,19 +3751,21 @@ def test_disable_ignore_roi_if_buy_signal(default_conf_usdt, limit_buy_order_usd
'ignore_roi_if_buy_signal': False
}
freqtrade = FreqtradeBot(default_conf_usdt)
- patch_get_signal(freqtrade)
+ patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
freqtrade.strategy.min_roi_reached = MagicMock(return_value=True)
freqtrade.enter_positions()
trade = Trade.query.first()
- trade.update(limit_buy_order_usdt)
+ trade.is_short = is_short
+ trade.update(limit_order[enter_side(is_short)])
# Sell due to min_roi_reached
- patch_get_signal(freqtrade, enter_long=True, exit_long=True)
+ patch_get_signal(freqtrade, enter_long=not is_short, exit_long=not is_short,
+ enter_short=is_short, exit_short=is_short)
assert freqtrade.handle_trade(trade) is True
# Test if buy-signal is absent
- patch_get_signal(freqtrade, enter_long=False, exit_long=True)
+ patch_get_signal(freqtrade, enter_long=False, exit_long=not is_short, exit_short=is_short)
assert freqtrade.handle_trade(trade) is True
assert trade.sell_reason == SellType.ROI.value
@@ -3497,7 +3845,7 @@ def test_get_real_amount_no_trade(default_conf_usdt, buy_order_fee, caplog, mock
)
-@pytest.mark.parametrize(
+@ pytest.mark.parametrize(
'fee_par,fee_reduction_amount,use_ticker_usdt_rate,expected_log', [
# basic, amount does not change
({'cost': 0.008, 'currency': 'ETH'}, 0, False, None),
@@ -3550,7 +3898,7 @@ def test_get_real_amount(
assert log_has(expected_log, caplog)
-@pytest.mark.parametrize(
+@ pytest.mark.parametrize(
'fee_cost, fee_currency, fee_reduction_amount, expected_fee, expected_log_amount', [
# basic, amount is reduced by fee
(None, None, 0.001, 0.001, 7.992),
@@ -3681,7 +4029,7 @@ def test_get_real_amount_wrong_amount_rounding(default_conf_usdt, trades_for_ord
)
-def test_get_real_amount_open_trade(default_conf_usdt, fee, mocker):
+def test_get_real_amount_open_trade_usdt(default_conf_usdt, fee, mocker):
amount = 12345
trade = Trade(
pair='LTC/ETH',
@@ -3702,7 +4050,7 @@ def test_get_real_amount_open_trade(default_conf_usdt, fee, mocker):
assert freqtrade.get_real_amount(trade, order) == amount
-@pytest.mark.parametrize('amount,fee_abs,wallet,amount_exp', [
+@ pytest.mark.parametrize('amount,fee_abs,wallet,amount_exp', [
(8.0, 0.0, 10, 8),
(8.0, 0.0, 0, 8),
(8.0, 0.1, 0, 7.9),
@@ -3731,13 +4079,17 @@ def test_apply_fee_conditional(default_conf_usdt, fee, mocker,
assert walletmock.call_count == 1
-@pytest.mark.parametrize("delta, is_high_delta", [
+@ pytest.mark.parametrize("delta, is_high_delta", [
(0.1, False),
(100, True),
])
+@ pytest.mark.parametrize('is_short, open_rate', [
+ (False, 2.0),
+ (True, 2.02),
+])
def test_order_book_depth_of_market(
- default_conf_usdt, ticker_usdt, limit_buy_order_usdt_open, limit_buy_order_usdt,
- fee, mocker, order_book_l2, delta, is_high_delta
+ default_conf_usdt, ticker_usdt, limit_order, limit_order_open,
+ fee, mocker, order_book_l2, delta, is_high_delta, is_short, open_rate
):
default_conf_usdt['bid_strategy']['check_depth_of_market']['enabled'] = True
default_conf_usdt['bid_strategy']['check_depth_of_market']['bids_to_ask_delta'] = delta
@@ -3747,20 +4099,21 @@ def test_order_book_depth_of_market(
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt,
- create_order=MagicMock(return_value=limit_buy_order_usdt_open),
+ create_order=MagicMock(return_value=limit_order_open[enter_side(is_short)]),
get_fee=fee,
)
# Save state of current whitelist
whitelist = deepcopy(default_conf_usdt['exchange']['pair_whitelist'])
freqtrade = FreqtradeBot(default_conf_usdt)
- patch_get_signal(freqtrade)
+ patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
freqtrade.enter_positions()
trade = Trade.query.first()
if is_high_delta:
assert trade is None
else:
+ trade.is_short = is_short
assert trade is not None
assert trade.stake_amount == 60.0
assert trade.is_open
@@ -3770,13 +4123,13 @@ def test_order_book_depth_of_market(
assert len(Trade.query.all()) == 1
# Simulate fulfilled LIMIT_BUY order for trade
- trade.update(limit_buy_order_usdt)
+ trade.update(limit_order_open[enter_side(is_short)])
- assert trade.open_rate == 2.0
+ assert trade.open_rate == open_rate # TODO-lev: double check
assert whitelist == default_conf_usdt['exchange']['pair_whitelist']
-@pytest.mark.parametrize('exception_thrown,ask,last,order_book_top,order_book', [
+@ pytest.mark.parametrize('exception_thrown,ask,last,order_book_top,order_book', [
(False, 0.045, 0.046, 2, None),
(True, 0.042, 0.046, 1, {'bids': [[]], 'asks': [[]]})
])
@@ -3809,7 +4162,7 @@ def test_order_book_bid_strategy1(mocker, default_conf_usdt, order_book_l2, exce
assert ticker_usdt_mock.call_count == 0
-def test_check_depth_of_market_buy(default_conf_usdt, mocker, order_book_l2) -> None:
+def test_check_depth_of_market(default_conf_usdt, mocker, order_book_l2) -> None:
"""
test check depth of market
"""
@@ -3826,11 +4179,12 @@ def test_check_depth_of_market_buy(default_conf_usdt, mocker, order_book_l2) ->
freqtrade = FreqtradeBot(default_conf_usdt)
conf = default_conf_usdt['bid_strategy']['check_depth_of_market']
- assert freqtrade._check_depth_of_market_buy('ETH/USDT', conf) is False
+ assert freqtrade._check_depth_of_market('ETH/BTC', conf, side=SignalDirection.LONG) is False
+@ pytest.mark.parametrize('is_short', [False, True])
def test_order_book_ask_strategy(
- default_conf_usdt, limit_buy_order_usdt_open, limit_buy_order_usdt, fee,
+ default_conf_usdt, limit_buy_order_usdt_open, limit_buy_order_usdt, fee, is_short,
limit_sell_order_usdt_open, mocker, order_book_l2, caplog) -> None:
"""
test order book ask strategy
@@ -3868,7 +4222,10 @@ def test_order_book_ask_strategy(
freqtrade.wallets.update()
assert trade.is_open is True
- patch_get_signal(freqtrade, enter_long=False, exit_long=True)
+ if is_short:
+ patch_get_signal(freqtrade, enter_long=False, exit_short=True)
+ else:
+ patch_get_signal(freqtrade, enter_long=False, exit_long=True)
assert freqtrade.handle_trade(trade) is True
assert trade.close_rate_requested == order_book_l2.return_value['asks'][0][0]
@@ -3906,7 +4263,7 @@ def test_startup_trade_reinit(default_conf_usdt, edge_conf, mocker):
assert reinit_mock.call_count == 0
-@pytest.mark.usefixtures("init_persistence")
+@ pytest.mark.usefixtures("init_persistence")
def test_sync_wallet_dry_run(mocker, default_conf_usdt, ticker_usdt, fee, limit_buy_order_usdt_open,
caplog):
default_conf_usdt['dry_run'] = True
@@ -3939,38 +4296,46 @@ def test_sync_wallet_dry_run(mocker, default_conf_usdt, ticker_usdt, fee, limit_
caplog)
-@pytest.mark.usefixtures("init_persistence")
-def test_cancel_all_open_orders(mocker, default_conf_usdt, fee, limit_buy_order_usdt,
- limit_sell_order_usdt):
+@ pytest.mark.usefixtures("init_persistence")
+@ pytest.mark.parametrize("is_short,buy_calls,sell_calls", [
+ (False, 1, 2),
+ (True, 2, 1),
+])
+def test_cancel_all_open_orders(mocker, default_conf_usdt, fee, limit_order, limit_order_open,
+ is_short, buy_calls, sell_calls):
default_conf_usdt['cancel_open_orders_on_exit'] = True
- mocker.patch('freqtrade.exchange.Exchange.fetch_order',
- side_effect=[
- ExchangeError(),
- limit_sell_order_usdt,
- limit_buy_order_usdt,
- limit_sell_order_usdt
- ])
+ mocker.patch(
+ 'freqtrade.exchange.Exchange.fetch_order',
+ side_effect=[
+ ExchangeError(),
+ limit_order[exit_side(is_short)],
+ limit_order_open[enter_side(is_short)],
+ limit_order_open[exit_side(is_short)],
+ ]
+ )
buy_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_cancel_enter')
sell_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_cancel_exit')
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
- create_mock_trades(fee)
+ create_mock_trades(fee, is_short=is_short)
trades = Trade.query.all()
assert len(trades) == MOCK_TRADE_COUNT
freqtrade.cancel_all_open_orders()
- assert buy_mock.call_count == 1
- assert sell_mock.call_count == 2
+ assert buy_mock.call_count == buy_calls
+ assert sell_mock.call_count == sell_calls
-@pytest.mark.usefixtures("init_persistence")
-def test_check_for_open_trades(mocker, default_conf_usdt, fee):
+@ pytest.mark.usefixtures("init_persistence")
+@ pytest.mark.parametrize("is_short", [False, True])
+def test_check_for_open_trades(mocker, default_conf_usdt, fee, is_short):
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
freqtrade.check_for_open_trades()
assert freqtrade.rpc.send_msg.call_count == 0
- create_mock_trades(fee)
+ create_mock_trades(fee, is_short)
trade = Trade.query.first()
+ trade.is_short = is_short
trade.is_open = True
freqtrade.check_for_open_trades()
@@ -3978,10 +4343,11 @@ def test_check_for_open_trades(mocker, default_conf_usdt, fee):
assert 'Handle these trades manually' in freqtrade.rpc.send_msg.call_args[0][0]['status']
-@pytest.mark.usefixtures("init_persistence")
-def test_startup_update_open_orders(mocker, default_conf_usdt, fee, caplog):
+@ pytest.mark.parametrize("is_short", [False, True])
+@ pytest.mark.usefixtures("init_persistence")
+def test_startup_update_open_orders(mocker, default_conf_usdt, fee, caplog, is_short):
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
- create_mock_trades(fee)
+ create_mock_trades(fee, is_short=is_short)
freqtrade.startup_update_open_orders()
assert not log_has_re(r"Error updating Order .*", caplog)
@@ -3994,7 +4360,7 @@ def test_startup_update_open_orders(mocker, default_conf_usdt, fee, caplog):
caplog.clear()
assert len(Order.get_open_orders()) == 3
- matching_buy_order = mock_order_4()
+ matching_buy_order = mock_order_4(is_short=is_short)
matching_buy_order.update({
'status': 'closed',
})
@@ -4004,8 +4370,9 @@ def test_startup_update_open_orders(mocker, default_conf_usdt, fee, caplog):
assert len(Order.get_open_orders()) == 2
-@pytest.mark.usefixtures("init_persistence")
-def test_update_closed_trades_without_assigned_fees(mocker, default_conf_usdt, fee):
+@ pytest.mark.usefixtures("init_persistence")
+@ pytest.mark.parametrize("is_short", [False, True])
+def test_update_closed_trades_without_assigned_fees(mocker, default_conf_usdt, fee, is_short):
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
def patch_with_fee(order):
@@ -4015,19 +4382,20 @@ def test_update_closed_trades_without_assigned_fees(mocker, default_conf_usdt, f
mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order',
side_effect=[
- patch_with_fee(mock_order_2_sell()),
- patch_with_fee(mock_order_3_sell()),
- patch_with_fee(mock_order_1()),
- patch_with_fee(mock_order_2()),
- patch_with_fee(mock_order_3()),
- patch_with_fee(mock_order_4()),
+ patch_with_fee(mock_order_2_sell(is_short=is_short)),
+ patch_with_fee(mock_order_3_sell(is_short=is_short)),
+ patch_with_fee(mock_order_1(is_short=is_short)),
+ patch_with_fee(mock_order_2(is_short=is_short)),
+ patch_with_fee(mock_order_3(is_short=is_short)),
+ patch_with_fee(mock_order_4(is_short=is_short)),
]
)
- create_mock_trades(fee)
+ create_mock_trades(fee, is_short=is_short)
trades = Trade.get_trades().all()
assert len(trades) == MOCK_TRADE_COUNT
for trade in trades:
+ trade.is_short = is_short
assert trade.fee_open_cost is None
assert trade.fee_open_currency is None
assert trade.fee_close_cost is None
@@ -4054,7 +4422,7 @@ def test_update_closed_trades_without_assigned_fees(mocker, default_conf_usdt, f
for trade in trades:
if trade.is_open:
# Exclude Trade 4 - as the order is still open.
- if trade.select_order('buy', False):
+ if trade.select_order(enter_side(is_short), False):
assert trade.fee_open_cost is not None
assert trade.fee_open_currency is not None
else:
@@ -4066,20 +4434,29 @@ def test_update_closed_trades_without_assigned_fees(mocker, default_conf_usdt, f
assert trade.fee_close_currency is not None
-@pytest.mark.usefixtures("init_persistence")
-def test_reupdate_enter_order_fees(mocker, default_conf_usdt, fee, caplog):
+@ pytest.mark.usefixtures("init_persistence")
+@ pytest.mark.parametrize("is_short", [False, True])
+def test_reupdate_enter_order_fees(mocker, default_conf_usdt, fee, caplog, is_short):
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
mock_uts = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.update_trade_state')
- create_mock_trades(fee)
+ create_mock_trades(fee, is_short=is_short)
trades = Trade.get_trades().all()
freqtrade.reupdate_enter_order_fees(trades[0])
- assert log_has_re(r"Trying to reupdate buy fees for .*", caplog)
+ assert log_has_re(
+ f"Trying to reupdate {enter_side(is_short)} "
+ r"fees for .*",
+ caplog
+ )
assert mock_uts.call_count == 1
assert mock_uts.call_args_list[0][0][0] == trades[0]
- assert mock_uts.call_args_list[0][0][1] == mock_order_1()['id']
- assert log_has_re(r"Updating buy-fee on trade .* for order .*\.", caplog)
+ assert mock_uts.call_args_list[0][0][1] == mock_order_1(is_short=is_short)['id']
+ assert log_has_re(
+ f"Updating {enter_side(is_short)}-fee on trade "
+ r".* for order .*\.",
+ caplog
+ )
mock_uts.reset_mock()
caplog.clear()
@@ -4094,21 +4471,24 @@ def test_reupdate_enter_order_fees(mocker, default_conf_usdt, fee, caplog):
amount=30,
open_rate=2.0,
exchange='binance',
+ is_short=is_short
)
Trade.query.session.add(trade)
freqtrade.reupdate_enter_order_fees(trade)
- assert log_has_re(r"Trying to reupdate buy fees for .*", caplog)
+ assert log_has_re(f"Trying to reupdate {enter_side(is_short)} fees for "
+ r".*", caplog)
assert mock_uts.call_count == 0
- assert not log_has_re(r"Updating buy-fee on trade .* for order .*\.", caplog)
+ assert not log_has_re(f"Updating {enter_side(is_short)}-fee on trade "
+ r".* for order .*\.", caplog)
-@pytest.mark.usefixtures("init_persistence")
+@ pytest.mark.usefixtures("init_persistence")
def test_handle_insufficient_funds(mocker, default_conf_usdt, fee):
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
mock_rlo = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.refind_lost_order')
mock_bof = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.reupdate_enter_order_fees')
- create_mock_trades(fee)
+ create_mock_trades(fee, is_short=False)
trades = Trade.get_trades().all()
# Trade 0 has only a open buy order, no closed order
@@ -4141,8 +4521,9 @@ def test_handle_insufficient_funds(mocker, default_conf_usdt, fee):
assert mock_bof.call_count == 1
-@pytest.mark.usefixtures("init_persistence")
-def test_refind_lost_order(mocker, default_conf_usdt, fee, caplog):
+@ pytest.mark.usefixtures("init_persistence")
+@ pytest.mark.parametrize("is_short", [False, True])
+def test_refind_lost_order(mocker, default_conf_usdt, fee, caplog, is_short):
caplog.set_level(logging.DEBUG)
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
mock_uts = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.update_trade_state')
@@ -4153,8 +4534,9 @@ def test_refind_lost_order(mocker, default_conf_usdt, fee, caplog):
def reset_open_orders(trade):
trade.open_order_id = None
trade.stoploss_order_id = None
+ trade.is_short = is_short
- create_mock_trades(fee)
+ create_mock_trades(fee, is_short=is_short)
trades = Trade.get_trades().all()
caplog.clear()
@@ -4166,7 +4548,7 @@ def test_refind_lost_order(mocker, default_conf_usdt, fee, caplog):
assert trade.stoploss_order_id is None
freqtrade.refind_lost_order(trade)
- order = mock_order_1()
+ order = mock_order_1(is_short=is_short)
assert log_has_re(r"Order Order(.*order_id=" + order['id'] + ".*) is no longer open.", caplog)
assert mock_fo.call_count == 0
assert mock_uts.call_count == 0
@@ -4184,7 +4566,7 @@ def test_refind_lost_order(mocker, default_conf_usdt, fee, caplog):
assert trade.stoploss_order_id is None
freqtrade.refind_lost_order(trade)
- order = mock_order_4()
+ order = mock_order_4(is_short=is_short)
assert log_has_re(r"Trying to refind Order\(.*", caplog)
assert mock_fo.call_count == 0
assert mock_uts.call_count == 0
@@ -4202,7 +4584,7 @@ def test_refind_lost_order(mocker, default_conf_usdt, fee, caplog):
assert trade.stoploss_order_id is None
freqtrade.refind_lost_order(trade)
- order = mock_order_5_stoploss()
+ order = mock_order_5_stoploss(is_short=is_short)
assert log_has_re(r"Trying to refind Order\(.*", caplog)
assert mock_fo.call_count == 1
assert mock_uts.call_count == 1
@@ -4221,7 +4603,7 @@ def test_refind_lost_order(mocker, default_conf_usdt, fee, caplog):
assert trade.stoploss_order_id is None
freqtrade.refind_lost_order(trade)
- order = mock_order_6_sell()
+ order = mock_order_6_sell(is_short=is_short)
assert log_has_re(r"Trying to refind Order\(.*", caplog)
assert mock_fo.call_count == 1
assert mock_uts.call_count == 1
@@ -4234,7 +4616,7 @@ def test_refind_lost_order(mocker, default_conf_usdt, fee, caplog):
# Test error case
mock_fo = mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order',
side_effect=ExchangeError())
- order = mock_order_5_stoploss()
+ order = mock_order_5_stoploss(is_short=is_short)
freqtrade.refind_lost_order(trades[4])
assert log_has(f"Error updating {order['id']}.", caplog)
@@ -4280,20 +4662,25 @@ def test_get_valid_price(mocker, default_conf_usdt) -> None:
assert valid_price_at_min_alwd < proposed_price
+def test_leverage_prep():
+ # TODO-lev
+ return
+
+
@pytest.mark.parametrize('trading_mode,calls,t1,t2', [
- (TradingMode.SPOT, 0, "2021-09-01 00:00:00", "2021-09-01 08:00:00"),
- (TradingMode.MARGIN, 0, "2021-09-01 00:00:00", "2021-09-01 08:00:00"),
- (TradingMode.FUTURES, 31, "2021-09-01 00:00:02", "2021-09-01 08:00:01"),
- (TradingMode.FUTURES, 32, "2021-09-01 00:00:00", "2021-09-01 08:00:01"),
- (TradingMode.FUTURES, 32, "2021-09-01 00:00:02", "2021-09-01 08:00:02"),
- (TradingMode.FUTURES, 33, "2021-09-01 00:00:00", "2021-09-01 08:00:02"),
- (TradingMode.FUTURES, 33, "2021-08-31 23:59:59", "2021-09-01 08:00:02"),
- (TradingMode.FUTURES, 33, "2021-08-31 23:59:59", "2021-09-01 08:00:03"),
- (TradingMode.FUTURES, 33, "2021-08-31 23:59:59", "2021-09-01 08:00:04"),
- (TradingMode.FUTURES, 33, "2021-08-31 23:59:59", "2021-09-01 08:00:05"),
- (TradingMode.FUTURES, 33, "2021-08-31 23:59:59", "2021-09-01 08:00:06"),
- (TradingMode.FUTURES, 33, "2021-08-31 23:59:59", "2021-09-01 08:00:07"),
- (TradingMode.FUTURES, 33, "2021-08-31 23:59:58", "2021-09-01 08:00:07"),
+ ('spot', 0, "2021-09-01 00:00:00", "2021-09-01 08:00:00"),
+ ('margin', 0, "2021-09-01 00:00:00", "2021-09-01 08:00:00"),
+ ('futures', 31, "2021-09-01 00:00:02", "2021-09-01 08:00:01"),
+ ('futures', 32, "2021-09-01 00:00:00", "2021-09-01 08:00:01"),
+ ('futures', 32, "2021-09-01 00:00:02", "2021-09-01 08:00:02"),
+ ('futures', 33, "2021-09-01 00:00:00", "2021-09-01 08:00:02"),
+ ('futures', 33, "2021-08-31 23:59:59", "2021-09-01 08:00:02"),
+ ('futures', 33, "2021-08-31 23:59:59", "2021-09-01 08:00:03"),
+ ('futures', 33, "2021-08-31 23:59:59", "2021-09-01 08:00:04"),
+ ('futures', 33, "2021-08-31 23:59:59", "2021-09-01 08:00:05"),
+ ('futures', 33, "2021-08-31 23:59:59", "2021-09-01 08:00:06"),
+ ('futures', 33, "2021-08-31 23:59:59", "2021-09-01 08:00:07"),
+ ('futures', 33, "2021-08-31 23:59:58", "2021-09-01 08:00:07"),
])
def test_update_funding_fees(mocker, default_conf, trading_mode, calls, time_machine,
t1, t2):
diff --git a/tests/test_persistence.py b/tests/test_persistence.py
index 7128fcd89..6f9bd6555 100644
--- a/tests/test_persistence.py
+++ b/tests/test_persistence.py
@@ -1514,11 +1514,12 @@ def test_adjust_min_max_rates(fee):
@pytest.mark.usefixtures("init_persistence")
@pytest.mark.parametrize('use_db', [True, False])
-def test_get_open(fee, use_db):
+@pytest.mark.parametrize('is_short', [True, False])
+def test_get_open(fee, is_short, use_db):
Trade.use_db = use_db
Trade.reset_trades()
- create_mock_trades(fee, use_db)
+ create_mock_trades(fee, is_short, use_db)
assert len(Trade.get_open_trades()) == 4
Trade.use_db = True
@@ -1874,14 +1875,15 @@ def test_fee_updated(fee):
@pytest.mark.usefixtures("init_persistence")
+@pytest.mark.parametrize('is_short', [True, False])
@pytest.mark.parametrize('use_db', [True, False])
-def test_total_open_trades_stakes(fee, use_db):
+def test_total_open_trades_stakes(fee, is_short, use_db):
Trade.use_db = use_db
Trade.reset_trades()
res = Trade.total_open_trades_stakes()
assert res == 0
- create_mock_trades(fee, use_db)
+ create_mock_trades(fee, is_short, use_db)
res = Trade.total_open_trades_stakes()
assert res == 0.004
@@ -1889,6 +1891,7 @@ def test_total_open_trades_stakes(fee, use_db):
@pytest.mark.usefixtures("init_persistence")
+# TODO-lev: @pytest.mark.parametrize('is_short', [True, False])
@pytest.mark.parametrize('use_db', [True, False])
def test_get_total_closed_profit(fee, use_db):
@@ -1896,7 +1899,7 @@ def test_get_total_closed_profit(fee, use_db):
Trade.reset_trades()
res = Trade.get_total_closed_profit()
assert res == 0
- create_mock_trades(fee, use_db)
+ create_mock_trades(fee, False, use_db)
res = Trade.get_total_closed_profit()
assert res == 0.000739127
@@ -1904,11 +1907,12 @@ def test_get_total_closed_profit(fee, use_db):
@pytest.mark.usefixtures("init_persistence")
+# TODO-lev: @pytest.mark.parametrize('is_short', [True, False])
@pytest.mark.parametrize('use_db', [True, False])
def test_get_trades_proxy(fee, use_db):
Trade.use_db = use_db
Trade.reset_trades()
- create_mock_trades(fee, use_db)
+ create_mock_trades(fee, False, use_db)
trades = Trade.get_trades_proxy()
assert len(trades) == 6
@@ -1937,9 +1941,10 @@ def test_get_trades_backtest():
@pytest.mark.usefixtures("init_persistence")
+# @pytest.mark.parametrize('is_short', [True, False])
def test_get_overall_performance(fee):
- create_mock_trades(fee)
+ create_mock_trades(fee, False)
res = Trade.get_overall_performance()
assert len(res) == 2
@@ -1949,12 +1954,13 @@ def test_get_overall_performance(fee):
@pytest.mark.usefixtures("init_persistence")
+# TODO-lev: @pytest.mark.parametrize('is_short', [True, False])
def test_get_best_pair(fee):
res = Trade.get_best_pair()
assert res is None
- create_mock_trades(fee)
+ create_mock_trades(fee, False)
res = Trade.get_best_pair()
assert len(res) == 2
assert res[0] == 'XRP/BTC'
@@ -2036,8 +2042,9 @@ def test_update_order_from_ccxt(caplog):
@pytest.mark.usefixtures("init_persistence")
+# TODO-lev: @pytest.mark.parametrize('is_short', [True, False])
def test_select_order(fee):
- create_mock_trades(fee)
+ create_mock_trades(fee, False)
trades = Trade.get_trades().all()