diff --git a/freqtrade/commands/data_commands.py b/freqtrade/commands/data_commands.py index 0c6f48088..95ad67a65 100644 --- a/freqtrade/commands/data_commands.py +++ b/freqtrade/commands/data_commands.py @@ -85,7 +85,7 @@ def start_download_data(args: Dict[str, Any]) -> None: new_pairs_days=config['new_pairs_days'], erase=bool(config.get('erase')), data_format=config['dataformat_ohlcv'], trading_mode=config.get('trading_mode', 'spot'), - ) + ) except KeyboardInterrupt: sys.exit("SIGINT received, aborting ...") diff --git a/freqtrade/enums/candletype.py b/freqtrade/enums/candletype.py index 0188650f6..9d05ff6d7 100644 --- a/freqtrade/enums/candletype.py +++ b/freqtrade/enums/candletype.py @@ -8,8 +8,10 @@ class CandleType(str, Enum): MARK = "mark" INDEX = "index" PREMIUMINDEX = "premiumIndex" - # TODO-lev: not sure this belongs here, as the datatype is really different + + # TODO: Could take up less memory if these weren't a CandleType FUNDING_RATE = "funding_rate" + # BORROW_RATE = "borrow_rate" # * unimplemented @staticmethod def from_string(value: str) -> 'CandleType': diff --git a/freqtrade/enums/rpcmessagetype.py b/freqtrade/enums/rpcmessagetype.py index 663b37b83..661f9ce5c 100644 --- a/freqtrade/enums/rpcmessagetype.py +++ b/freqtrade/enums/rpcmessagetype.py @@ -10,16 +10,19 @@ class RPCMessageType(Enum): 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' + # TODO: The below messagetypes should be renamed to "exit"! + # Careful - has an impact on webhooks, therefore needs proper communication + SELL = 'sell' + SELL_FILL = 'sell_fill' + SELL_CANCEL = 'sell_cancel' + + PROTECTION_TRIGGER = 'protection_trigger' + PROTECTION_TRIGGER_GLOBAL = 'protection_trigger_global' + def __repr__(self): return self.value diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 30e70461e..c66c7384d 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -503,7 +503,7 @@ class Exchange: # Note: ccxt has BaseCurrency/QuoteCurrency format for pairs if self.markets and pair not in self.markets: raise OperationalException( - f'Pair {pair} is not available on {self.name}. ' + f'Pair {pair} is not available on {self.name} {self.trading_mode.value}. ' f'Please remove {pair} from your whitelist.') # From ccxt Documentation: @@ -1533,7 +1533,6 @@ class Exchange: :return: Dict of [{(pair, timeframe): Dataframe}] """ logger.debug("Refreshing candle (OHLCV) data for %d pairs", len(pair_list)) - # TODO-lev: maybe depend this on candle type? drop_incomplete = self._ohlcv_partial_candle if drop_incomplete is None else drop_incomplete input_coroutines = [] cached_pairs = [] diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 0906276f9..597ab98d6 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -740,6 +740,9 @@ class FreqtradeBot(LoggingMixin): # in case of FOK the order may be filled immediately and fully elif order_status == 'closed': + # TODO-lev: Evaluate this. Why is setting stake_amount here necessary? + # it should never change in theory - and in case of leveraged orders, + # may be the leveraged amount. stake_amount = order['cost'] amount = safe_value_fallback(order, 'filled', 'amount') enter_limit_filled_price = safe_value_fallback(order, 'average', 'price') @@ -1288,6 +1291,7 @@ class FreqtradeBot(LoggingMixin): # * Check edge cases, we don't want to make leverage > 1.0 if we don't have to # * (for leverage modes which aren't isolated futures) + # TODO-lev: The below calculation needs to include leverage ... trade.stake_amount = trade.amount * trade.open_rate self.update_trade_state(trade, trade.open_order_id, corder) @@ -1736,7 +1740,7 @@ class FreqtradeBot(LoggingMixin): trade.update_fee(fee_cost, fee_currency, fee_rate, order.get('side', '')) if not isclose(amount, order_amount, abs_tol=constants.MATH_CLOSE_PREC): - # TODO-lev: leverage? + # * Leverage could be a cause for this warning, leverage hasn't been thoroughly tested logger.warning(f"Amount {amount} does not match amount {trade.amount}") raise DependencyException("Half bought? Amounts don't match") diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 719aea8aa..c1dad72d5 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -538,7 +538,6 @@ class Backtesting: sell_candle_time: datetime = sell_row[DATE_IDX].to_pydatetime() if self.trading_mode == TradingMode.FUTURES: - # TODO-lev: liquidation price? trade.funding_fees = self.exchange.calculate_funding_fees( self.futures_data[trade.pair], amount=trade.amount, diff --git a/freqtrade/templates/sample_short_strategy.py b/freqtrade/templates/sample_short_strategy.py index e9deba6af..bcb6c921e 100644 --- a/freqtrade/templates/sample_short_strategy.py +++ b/freqtrade/templates/sample_short_strategy.py @@ -15,7 +15,7 @@ import talib.abstract as ta import freqtrade.vendor.qtpylib.indicators as qtpylib -# TODO-lev: Create a meaningfull short strategy (not just revresed signs). +# TODO: Create a meaningfull short strategy (not just revresed signs). # This class is a sample. Feel free to customize it. class SampleShortStrategy(IStrategy): """ diff --git a/tests/optimize/test_backtest_detail.py b/tests/optimize/test_backtest_detail.py index ee13715f6..e0154160b 100644 --- a/tests/optimize/test_backtest_detail.py +++ b/tests/optimize/test_backtest_detail.py @@ -558,7 +558,7 @@ tc35 = BTContainer(data=[ stop_loss=-0.01, roi={"0": 0.10}, profit_perc=-0.01, custom_entry_price=7200, trades=[ BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=1) - ] +] ) # Test 36: Custom-entry-price around candle low @@ -697,7 +697,7 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None: backtesting._set_strategy(backtesting.strategylist[0]) backtesting.required_startup = 0 if data.leverage > 1.0: - # TODO-lev: Should we initialize this properly?? + # TODO: Should we initialize this properly?? backtesting._can_short = True backtesting.strategy.advise_entry = lambda a, m: frame backtesting.strategy.advise_exit = lambda a, m: frame diff --git a/tests/strategy/strats/strategy_test_v3.py b/tests/strategy/strats/strategy_test_v3.py index 5d689e0a1..a056b316c 100644 --- a/tests/strategy/strats/strategy_test_v3.py +++ b/tests/strategy/strats/strategy_test_v3.py @@ -71,7 +71,7 @@ class StrategyTestV3(IStrategy): protection_enabled = BooleanParameter(default=True) protection_cooldown_lookback = IntParameter([0, 50], default=30) - # TODO-lev: Can this work with protection tests? (replace HyperoptableStrategy implicitly ... ) + # TODO: Can this work with protection tests? (replace HyperoptableStrategy implicitly ... ) # @property # def protections(self): # prot = [] diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 39654e780..cc5cd1982 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -522,13 +522,11 @@ def test_create_trades_preopen(default_conf_usdt, ticker_usdt, fee, mocker, assert len(trades) == 4 -@pytest.mark.parametrize('is_short, open_rate', [ - (False, 2.0), - (True, 2.02) -]) +@pytest.mark.parametrize('is_short', [False, True]) def test_process_trade_creation(default_conf_usdt, ticker_usdt, limit_order, limit_order_open, - is_short, open_rate, fee, mocker, caplog + is_short, fee, mocker, caplog ) -> None: + ticker_side = 'ask' if is_short else 'bid' patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( @@ -554,8 +552,8 @@ def test_process_trade_creation(default_conf_usdt, ticker_usdt, limit_order, lim assert trade.is_open assert trade.open_date is not None assert trade.exchange == 'binance' - 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 trade.open_rate == ticker_usdt.return_value[ticker_side] + assert isclose(trade.amount, 60 / ticker_usdt.return_value[ticker_side]) assert log_has( f'{"Short" if is_short else "Long"} signal found: about create a new trade for ETH/USDT ' @@ -3275,9 +3273,9 @@ def test_execute_trade_exit_with_stoploss_on_exchange( assert rpc_mock.call_count == 3 -# TODO-lev: add short, RPC short, short fill -def test_may_execute_trade_exit_after_stoploss_on_exchange_hit(default_conf_usdt, ticker_usdt, fee, - mocker) -> None: +@pytest.mark.parametrize("is_short", [False, True]) +def test_may_execute_trade_exit_after_stoploss_on_exchange_hit( + default_conf_usdt, ticker_usdt, fee, mocker, is_short) -> None: default_conf_usdt['exchange']['name'] = 'binance' rpc_mock = patch_RPCManager(mocker) patch_exchange(mocker) @@ -3301,7 +3299,7 @@ def test_may_execute_trade_exit_after_stoploss_on_exchange_hit(default_conf_usdt freqtrade = FreqtradeBot(default_conf_usdt) freqtrade.strategy.order_types['stoploss_on_exchange'] = True - patch_get_signal(freqtrade) + patch_get_signal(freqtrade, enter_long=not is_short, enter_short=is_short) # Create some test data freqtrade.enter_positions() @@ -3315,7 +3313,7 @@ def test_may_execute_trade_exit_after_stoploss_on_exchange_hit(default_conf_usdt assert trade.stoploss_order_id == '123' assert trade.open_order_id is None - # Assuming stoploss on exchnage is hit + # Assuming stoploss on exchange is hit # stoploss_order_id should become None # and trade should be sold at the price of stoploss stoploss_executed = MagicMock(return_value={ @@ -3343,19 +3341,24 @@ def test_may_execute_trade_exit_after_stoploss_on_exchange_hit(default_conf_usdt assert trade.is_open is False assert trade.sell_reason == SellType.STOPLOSS_ON_EXCHANGE.value assert rpc_mock.call_count == 3 - assert rpc_mock.call_args_list[0][0][0]['type'] == RPCMessageType.BUY - assert rpc_mock.call_args_list[1][0][0]['type'] == RPCMessageType.BUY_FILL - assert rpc_mock.call_args_list[2][0][0]['type'] == RPCMessageType.SELL + if is_short: + assert rpc_mock.call_args_list[0][0][0]['type'] == RPCMessageType.SHORT + assert rpc_mock.call_args_list[1][0][0]['type'] == RPCMessageType.SHORT_FILL + assert rpc_mock.call_args_list[2][0][0]['type'] == RPCMessageType.SELL + + else: + assert rpc_mock.call_args_list[0][0][0]['type'] == RPCMessageType.BUY + assert rpc_mock.call_args_list[1][0][0]['type'] == RPCMessageType.BUY_FILL + assert rpc_mock.call_args_list[2][0][0]['type'] == RPCMessageType.SELL @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'), + "is_short,amount,current_rate,limit,profit_amount,profit_ratio,profit_or_loss", [ + (False, 30, 2.3, 2.2, 5.685, 0.09451372, 'profit'), + (True, 29.70297029, 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, + default_conf_usdt, ticker_usdt, fee, is_short, current_rate, amount, limit, profit_amount, profit_ratio, profit_or_loss, ticker_usdt_sell_up, mocker ) -> None: """ @@ -3375,6 +3378,7 @@ def test_execute_trade_exit_market_order( long: (65.835/60.15) - 1 = 0.0945137157107232 short: 1 - (68.48762376237624/59.85) = -0.1443211990371971 """ + open_rate = ticker_usdt.return_value['ask' if is_short else 'bid'] rpc_mock = patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( @@ -4241,14 +4245,13 @@ def test_apply_fee_conditional(default_conf_usdt, fee, mocker, (0.1, False), (100, True), ]) -@pytest.mark.parametrize('is_short, open_rate', [ - (False, 2.0), - (True, 2.02), -]) +@pytest.mark.parametrize('is_short', [False, True]) def test_order_book_depth_of_market( - 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, ticker_usdt, limit_order_open, + fee, mocker, order_book_l2, delta, is_high_delta, is_short ): + ticker_side = 'ask' if is_short else 'bid' + 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 patch_RPCManager(mocker) @@ -4283,7 +4286,7 @@ def test_order_book_depth_of_market( # Simulate fulfilled LIMIT_BUY order for trade trade.update(limit_order_open[enter_side(is_short)]) - assert trade.open_rate == open_rate # TODO-lev: double check + assert trade.open_rate == ticker_usdt.return_value[ticker_side] assert whitelist == default_conf_usdt['exchange']['pair_whitelist']