From d563bfc3d045c5f6f4d4aa5d0ea55327ce2bada5 Mon Sep 17 00:00:00 2001 From: lukasgor Date: Fri, 11 Feb 2022 13:10:44 +0100 Subject: [PATCH 01/43] feature: add buy tag to forcebuy --- freqtrade/rpc/api_server/api_schemas.py | 1 + freqtrade/rpc/api_server/api_v1.py | 3 ++- freqtrade/rpc/rpc.py | 5 +++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py index b3912a2b5..bab4a8c45 100644 --- a/freqtrade/rpc/api_server/api_schemas.py +++ b/freqtrade/rpc/api_server/api_schemas.py @@ -280,6 +280,7 @@ class ForceBuyPayload(BaseModel): price: Optional[float] ordertype: Optional[OrderTypeValues] stakeamount: Optional[float] + buy_tag: Optional[str] class ForceSellPayload(BaseModel): diff --git a/freqtrade/rpc/api_server/api_v1.py b/freqtrade/rpc/api_server/api_v1.py index 256f82a8c..c155da416 100644 --- a/freqtrade/rpc/api_server/api_v1.py +++ b/freqtrade/rpc/api_server/api_v1.py @@ -136,8 +136,9 @@ def show_config(rpc: Optional[RPC] = Depends(get_rpc_optional), config=Depends(g def forcebuy(payload: ForceBuyPayload, rpc: RPC = Depends(get_rpc)): ordertype = payload.ordertype.value if payload.ordertype else None stake_amount = payload.stakeamount if payload.stakeamount else None + buy_tag = payload.buy_tag if payload.buy_tag else None - trade = rpc._rpc_forcebuy(payload.pair, payload.price, ordertype, stake_amount) + trade = rpc._rpc_forcebuy(payload.pair, payload.price, ordertype, stake_amount, buy_tag) if trade: return ForceBuyResponse.parse_obj(trade.to_json()) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index b9414e3f1..377134542 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -717,7 +717,8 @@ class RPC: return {'result': f'Created sell order for trade {trade_id}.'} def _rpc_forcebuy(self, pair: str, price: Optional[float], order_type: Optional[str] = None, - stake_amount: Optional[float] = None) -> Optional[Trade]: + stake_amount: Optional[float] = None, + buy_tag: Optional[str] = None) -> Optional[Trade]: """ Handler for forcebuy Buys a pair trade at the given or current price @@ -751,7 +752,7 @@ class RPC: order_type = self._freqtrade.strategy.order_types.get( 'forcebuy', self._freqtrade.strategy.order_types['buy']) if self._freqtrade.execute_entry(pair, stake_amount, price, - ordertype=order_type, trade=trade): + ordertype=order_type, trade=trade, buy_tag=buy_tag): Trade.commit() trade = Trade.get_trades([Trade.is_open.is_(True), Trade.pair == pair]).first() return trade From 6511b3bec2d174494adb815f63a31c06d08f7fd3 Mon Sep 17 00:00:00 2001 From: lukasgor Date: Fri, 11 Feb 2022 15:31:15 +0100 Subject: [PATCH 02/43] fix: rename buy_tag to entry_tag --- freqtrade/rpc/api_server/api_schemas.py | 2 +- freqtrade/rpc/api_server/api_v1.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py index bab4a8c45..ede5dcf0b 100644 --- a/freqtrade/rpc/api_server/api_schemas.py +++ b/freqtrade/rpc/api_server/api_schemas.py @@ -280,7 +280,7 @@ class ForceBuyPayload(BaseModel): price: Optional[float] ordertype: Optional[OrderTypeValues] stakeamount: Optional[float] - buy_tag: Optional[str] + entry_tag: Optional[str] class ForceSellPayload(BaseModel): diff --git a/freqtrade/rpc/api_server/api_v1.py b/freqtrade/rpc/api_server/api_v1.py index c155da416..f072e2b14 100644 --- a/freqtrade/rpc/api_server/api_v1.py +++ b/freqtrade/rpc/api_server/api_v1.py @@ -136,9 +136,9 @@ def show_config(rpc: Optional[RPC] = Depends(get_rpc_optional), config=Depends(g def forcebuy(payload: ForceBuyPayload, rpc: RPC = Depends(get_rpc)): ordertype = payload.ordertype.value if payload.ordertype else None stake_amount = payload.stakeamount if payload.stakeamount else None - buy_tag = payload.buy_tag if payload.buy_tag else None + entry_tag = payload.entry_tag if payload.entry_tag else None - trade = rpc._rpc_forcebuy(payload.pair, payload.price, ordertype, stake_amount, buy_tag) + trade = rpc._rpc_forcebuy(payload.pair, payload.price, ordertype, stake_amount, entry_tag) if trade: return ForceBuyResponse.parse_obj(trade.to_json()) From c9cfc246f1599ef79f13fcb79ea93d806171b8b2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 11 Feb 2022 19:37:35 +0100 Subject: [PATCH 03/43] Sort /forcebuy pairs alphabetically, add cancel button closes #6389 --- freqtrade/rpc/telegram.py | 16 ++++++++++------ tests/rpc/test_rpc_telegram.py | 3 ++- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index c7248f354..0a634ffae 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -914,10 +914,11 @@ class Telegram(RPCHandler): self._send_msg(str(e)) def _forcebuy_action(self, pair, price=None): - try: - self._rpc._rpc_forcebuy(pair, price) - except RPCException as e: - self._send_msg(str(e)) + if pair != 'cancel': + try: + self._rpc._rpc_forcebuy(pair, price) + except RPCException as e: + self._send_msg(str(e)) def _forcebuy_inline(self, update: Update, _: CallbackContext) -> None: if update.callback_query: @@ -947,10 +948,13 @@ class Telegram(RPCHandler): self._forcebuy_action(pair, price) else: whitelist = self._rpc._rpc_whitelist()['whitelist'] - pairs = [InlineKeyboardButton(text=pair, callback_data=pair) for pair in whitelist] + pair_buttons = [ + InlineKeyboardButton(text=pair, callback_data=pair) for pair in sorted(whitelist)] + buttons_aligned = self._layout_inline_keyboard(pair_buttons) + buttons_aligned.append([InlineKeyboardButton(text='Cancel', callback_data='cancel')]) self._send_msg(msg="Which pair?", - keyboard=self._layout_inline_keyboard(pairs)) + keyboard=buttons_aligned) @authorized_only def _trades(self, update: Update, context: CallbackContext) -> None: diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 796147609..67a6c72fe 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -1260,7 +1260,8 @@ def test_forcebuy_no_pair(default_conf, update, mocker) -> None: assert msg_mock.call_args_list[0][1]['msg'] == 'Which pair?' # assert msg_mock.call_args_list[0][1]['callback_query_handler'] == 'forcebuy' keyboard = msg_mock.call_args_list[0][1]['keyboard'] - assert reduce(lambda acc, x: acc + len(x), keyboard, 0) == 4 + # One additional button - cancel + assert reduce(lambda acc, x: acc + len(x), keyboard, 0) == 5 update = MagicMock() update.callback_query = MagicMock() update.callback_query.data = 'XRP/USDT' From 08803524bd27c8a5c06c4d92cc81b4cf032a9bce Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 12 Feb 2022 15:21:36 +0100 Subject: [PATCH 04/43] align variable naming to use current_time --- freqtrade/strategy/interface.py | 11 ++++++----- tests/strategy/test_interface.py | 6 ++++-- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 0bd7834e2..2f3657059 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -687,7 +687,7 @@ class IStrategy(ABC, HyperStrategyMixin): else: return False - def should_sell(self, trade: Trade, rate: float, date: datetime, buy: bool, + def should_sell(self, trade: Trade, rate: float, current_time: datetime, buy: bool, sell: bool, low: float = None, high: float = None, force_stoploss: float = 0) -> SellCheckTuple: """ @@ -704,7 +704,8 @@ class IStrategy(ABC, HyperStrategyMixin): trade.adjust_min_max_rates(high or current_rate, low or current_rate) stoplossflag = self.stop_loss_reached(current_rate=current_rate, trade=trade, - current_time=date, current_profit=current_profit, + current_time=current_time, + current_profit=current_profit, force_stoploss=force_stoploss, low=low, high=high) # Set current rate to high for backtesting sell @@ -714,7 +715,7 @@ class IStrategy(ABC, HyperStrategyMixin): # if buy signal and ignore_roi is set, we don't need to evaluate min_roi. roi_reached = (not (buy and self.ignore_roi_if_buy_signal) and self.min_roi_reached(trade=trade, current_profit=current_profit, - current_time=date)) + current_time=current_time)) sell_signal = SellType.NONE custom_reason = '' @@ -730,8 +731,8 @@ class IStrategy(ABC, HyperStrategyMixin): sell_signal = SellType.SELL_SIGNAL else: custom_reason = strategy_safe_wrapper(self.custom_sell, default_retval=False)( - pair=trade.pair, trade=trade, current_time=date, current_rate=current_rate, - current_profit=current_profit) + pair=trade.pair, trade=trade, current_time=current_time, + current_rate=current_rate, current_profit=current_profit) if custom_reason: sell_signal = SellType.CUSTOM_SELL if isinstance(custom_reason, str): diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index fd1c2753f..174ce95c6 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -437,7 +437,8 @@ def test_stop_loss_reached(default_conf, fee, profit, adjusted, expected, traili strategy.custom_stoploss = custom_stop now = arrow.utcnow().datetime - sl_flag = strategy.stop_loss_reached(current_rate=trade.open_rate * (1 + profit), trade=trade, + current_rate = trade.open_rate * (1 + profit) + sl_flag = strategy.stop_loss_reached(current_rate=current_rate, trade=trade, current_time=now, current_profit=profit, force_stoploss=0, high=None) assert isinstance(sl_flag, SellCheckTuple) @@ -447,8 +448,9 @@ def test_stop_loss_reached(default_conf, fee, profit, adjusted, expected, traili else: assert sl_flag.sell_flag is True assert round(trade.stop_loss, 2) == adjusted + current_rate2 = trade.open_rate * (1 + profit2) - sl_flag = strategy.stop_loss_reached(current_rate=trade.open_rate * (1 + profit2), trade=trade, + sl_flag = strategy.stop_loss_reached(current_rate=current_rate2, trade=trade, current_time=now, current_profit=profit2, force_stoploss=0, high=None) assert sl_flag.sell_type == expected2 From 119d4d5204534ba25b0d2d10510f4f21f04094ee Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 12 Feb 2022 17:06:03 +0100 Subject: [PATCH 05/43] select_order should use ft_order_side, not the exchange specific one --- freqtrade/persistence/models.py | 4 ++-- tests/test_persistence.py | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index dfa98d97f..5f2db1050 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -659,13 +659,13 @@ class LocalTrade(): self, order_side: str = None, is_open: Optional[bool] = None) -> Optional[Order]: """ Finds latest order for this orderside and status - :param order_side: Side of the order (either 'buy' or 'sell') + :param order_side: ft_order_side of the order (either 'buy', 'sell' or 'stoploss') :param is_open: Only search for open orders? :return: latest Order object if it exists, else None """ orders = self.orders if order_side: - orders = [o for o in self.orders if o.side == order_side] + orders = [o for o in self.orders if o.ft_order_side == order_side] if is_open is not None: orders = [o for o in orders if o.ft_is_open == is_open] if len(orders) > 0: diff --git a/tests/test_persistence.py b/tests/test_persistence.py index 97851fdc4..b8f7a3336 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -1277,11 +1277,14 @@ def test_select_order(fee): order = trades[4].select_order('buy', False) assert order is not None + trades[4].orders[1].ft_order_side = 'sell' order = trades[4].select_order('sell', True) assert order is not None + + trades[4].orders[1].ft_order_side = 'stoploss' + order = trades[4].select_order('stoploss', None) + assert order is not None assert order.ft_order_side == 'stoploss' - order = trades[4].select_order('sell', False) - assert order is None def test_Trade_object_idem(): From c769e9757d6454c921806b279c7fc5997b90381b Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 12 Feb 2022 20:13:12 +0100 Subject: [PATCH 06/43] Improve "order refind" to also work for stoploss orders --- freqtrade/freqtradebot.py | 28 ++------------ tests/test_freqtradebot.py | 77 ++++++++++---------------------------- 2 files changed, 23 insertions(+), 82 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 279bb6161..fce85baa3 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -298,28 +298,6 @@ class FreqtradeBot(LoggingMixin): self.update_trade_state(trade, order.order_id, send_msg=False) def handle_insufficient_funds(self, trade: Trade): - """ - Determine if we ever opened a sell order for this trade. - If not, try update buy fees - otherwise "refind" the open order we obviously lost. - """ - sell_order = trade.select_order('sell', None) - if sell_order: - self.refind_lost_order(trade) - else: - self.reupdate_enter_order_fees(trade) - - def reupdate_enter_order_fees(self, trade: Trade): - """ - 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) - if order: - logger.info(f"Updating buy-fee on trade {trade} for order {order.order_id}.") - self.update_trade_state(trade, order.order_id, send_msg=False) - - def refind_lost_order(self, trade): """ Try refinding a lost trade. Only used when InsufficientFunds appears on sell orders (stoploss or sell). @@ -332,9 +310,6 @@ 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': - # Skip buy side - this is handled by reupdate_buy_order_fees - continue try: fo = self.exchange.fetch_order_or_stoploss_order(order.order_id, order.ft_pair, order.ft_order_side == 'stoploss') @@ -346,6 +321,9 @@ class FreqtradeBot(LoggingMixin): if fo and fo['status'] == 'open': # Assume this as the open order trade.open_order_id = order.order_id + elif order.ft_order_side == 'buy': + if fo and fo['status'] == 'open': + trade.open_order_id = order.order_id if fo: logger.info(f"Found {order} for trade {trade}.") self.update_trade_state(trade, order.order_id, fo, diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index a84616516..4bbf26362 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -4110,15 +4110,17 @@ def test_reupdate_enter_order_fees(mocker, default_conf_usdt, fee, caplog): freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) mock_uts = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.update_trade_state') + mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order', + return_value={'status': 'open'}) create_mock_trades(fee) trades = Trade.get_trades().all() - freqtrade.reupdate_enter_order_fees(trades[0]) - assert log_has_re(r"Trying to reupdate buy fees for .*", caplog) + freqtrade.handle_insufficient_funds(trades[3]) + # assert log_has_re(r"Trying to reupdate buy 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][0] == trades[3] + assert mock_uts.call_args_list[0][0][1] == mock_order_4()['id'] + assert log_has_re(r"Trying to refind lost order for .*", caplog) mock_uts.reset_mock() caplog.clear() @@ -4136,52 +4138,13 @@ def test_reupdate_enter_order_fees(mocker, default_conf_usdt, fee, caplog): ) Trade.query.session.add(trade) - freqtrade.reupdate_enter_order_fees(trade) - assert log_has_re(r"Trying to reupdate buy fees for .*", caplog) + freqtrade.handle_insufficient_funds(trade) + # assert log_has_re(r"Trying to reupdate buy fees for .*", caplog) assert mock_uts.call_count == 0 - assert not log_has_re(r"Updating buy-fee on trade .* for order .*\.", caplog) @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) - trades = Trade.get_trades().all() - - # Trade 0 has only a open buy order, no closed order - freqtrade.handle_insufficient_funds(trades[0]) - assert mock_rlo.call_count == 0 - assert mock_bof.call_count == 1 - - mock_rlo.reset_mock() - mock_bof.reset_mock() - - # Trade 1 has closed buy and sell orders - freqtrade.handle_insufficient_funds(trades[1]) - assert mock_rlo.call_count == 1 - assert mock_bof.call_count == 0 - - mock_rlo.reset_mock() - mock_bof.reset_mock() - - # Trade 2 has closed buy and sell orders - freqtrade.handle_insufficient_funds(trades[2]) - assert mock_rlo.call_count == 1 - assert mock_bof.call_count == 0 - - mock_rlo.reset_mock() - mock_bof.reset_mock() - - # Trade 3 has an opne buy order - freqtrade.handle_insufficient_funds(trades[3]) - assert mock_rlo.call_count == 0 - assert mock_bof.call_count == 1 - - -@pytest.mark.usefixtures("init_persistence") -def test_refind_lost_order(mocker, default_conf_usdt, fee, caplog): +def test_handle_insufficient_funds(mocker, default_conf_usdt, fee, caplog): caplog.set_level(logging.DEBUG) freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) mock_uts = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.update_trade_state') @@ -4204,7 +4167,7 @@ def test_refind_lost_order(mocker, default_conf_usdt, fee, caplog): assert trade.open_order_id is None assert trade.stoploss_order_id is None - freqtrade.refind_lost_order(trade) + freqtrade.handle_insufficient_funds(trade) order = mock_order_1() assert log_has_re(r"Order Order(.*order_id=" + order['id'] + ".*) is no longer open.", caplog) assert mock_fo.call_count == 0 @@ -4222,13 +4185,13 @@ def test_refind_lost_order(mocker, default_conf_usdt, fee, caplog): assert trade.open_order_id is None assert trade.stoploss_order_id is None - freqtrade.refind_lost_order(trade) + freqtrade.handle_insufficient_funds(trade) order = mock_order_4() assert log_has_re(r"Trying to refind Order\(.*", caplog) - assert mock_fo.call_count == 0 - assert mock_uts.call_count == 0 - # No change to orderid - as update_trade_state is mocked - assert trade.open_order_id is None + assert mock_fo.call_count == 1 + assert mock_uts.call_count == 1 + # Found open buy order + assert trade.open_order_id is not None assert trade.stoploss_order_id is None caplog.clear() @@ -4240,11 +4203,11 @@ def test_refind_lost_order(mocker, default_conf_usdt, fee, caplog): assert trade.open_order_id is None assert trade.stoploss_order_id is None - freqtrade.refind_lost_order(trade) + freqtrade.handle_insufficient_funds(trade) order = mock_order_5_stoploss() assert log_has_re(r"Trying to refind Order\(.*", caplog) assert mock_fo.call_count == 1 - assert mock_uts.call_count == 1 + assert mock_uts.call_count == 2 # stoploss_order_id is "refound" and added to the trade assert trade.open_order_id is None assert trade.stoploss_order_id is not None @@ -4259,7 +4222,7 @@ def test_refind_lost_order(mocker, default_conf_usdt, fee, caplog): assert trade.open_order_id is None assert trade.stoploss_order_id is None - freqtrade.refind_lost_order(trade) + freqtrade.handle_insufficient_funds(trade) order = mock_order_6_sell() assert log_has_re(r"Trying to refind Order\(.*", caplog) assert mock_fo.call_count == 1 @@ -4275,7 +4238,7 @@ def test_refind_lost_order(mocker, default_conf_usdt, fee, caplog): side_effect=ExchangeError()) order = mock_order_5_stoploss() - freqtrade.refind_lost_order(trades[4]) + freqtrade.handle_insufficient_funds(trades[4]) assert log_has(f"Error updating {order['id']}.", caplog) From b1b8167b5e8278c76a4eacb2852c6e6d5fc08916 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 13 Feb 2022 19:14:57 +0100 Subject: [PATCH 07/43] Update stop documentation closes #6393 --- docs/stoploss.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/stoploss.md b/docs/stoploss.md index 4f8ac9e94..4d28846f1 100644 --- a/docs/stoploss.md +++ b/docs/stoploss.md @@ -2,6 +2,7 @@ The `stoploss` configuration parameter is loss as ratio that should trigger a sale. For example, value `-0.10` will cause immediate sell if the profit dips below -10% for a given trade. This parameter is optional. +Stoploss calculations do include fees, so a stoploss of -10% is placed exactly 10% below the entry point. Most of the strategy files already include the optimal `stoploss` value. @@ -30,7 +31,7 @@ These modes can be configured with these values: ### stoploss_on_exchange and stoploss_on_exchange_limit_ratio Enable or Disable stop loss on exchange. -If the stoploss is *on exchange* it means a stoploss limit order is placed on the exchange immediately after buy order happens successfully. This will protect you against sudden crashes in market as the order will be in the queue immediately and if market goes down then the order has more chance of being fulfilled. +If the stoploss is *on exchange* it means a stoploss limit order is placed on the exchange immediately after buy order fills. This will protect you against sudden crashes in market, as the order execution happens purely within the exchange, and has no potential network overhead. If `stoploss_on_exchange` uses limit orders, the exchange needs 2 prices, the stoploss_price and the Limit price. `stoploss` defines the stop-price where the limit order is placed - and limit should be slightly below this. From ca6291479420848d21a9389a397ea7fe6e8a00ed Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Feb 2022 03:01:28 +0000 Subject: [PATCH 08/43] Bump prompt-toolkit from 3.0.26 to 3.0.28 Bumps [prompt-toolkit](https://github.com/prompt-toolkit/python-prompt-toolkit) from 3.0.26 to 3.0.28. - [Release notes](https://github.com/prompt-toolkit/python-prompt-toolkit/releases) - [Changelog](https://github.com/prompt-toolkit/python-prompt-toolkit/blob/master/CHANGELOG) - [Commits](https://github.com/prompt-toolkit/python-prompt-toolkit/compare/3.0.26...3.0.28) --- updated-dependencies: - dependency-name: prompt-toolkit dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index ba20a169f..4710b800c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -41,6 +41,6 @@ psutil==5.9.0 colorama==0.4.4 # Building config files interactively questionary==1.10.0 -prompt-toolkit==3.0.26 +prompt-toolkit==3.0.28 # Extensions to datetime library python-dateutil==2.8.2 From be8accebd85b30d16eed7fef76f26a88152af1d7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Feb 2022 03:01:31 +0000 Subject: [PATCH 09/43] Bump plotly from 5.5.0 to 5.6.0 Bumps [plotly](https://github.com/plotly/plotly.py) from 5.5.0 to 5.6.0. - [Release notes](https://github.com/plotly/plotly.py/releases) - [Changelog](https://github.com/plotly/plotly.py/blob/master/CHANGELOG.md) - [Commits](https://github.com/plotly/plotly.py/compare/v5.5.0...v5.6.0) --- updated-dependencies: - dependency-name: plotly dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-plot.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-plot.txt b/requirements-plot.txt index 990edc3c8..bb2132f87 100644 --- a/requirements-plot.txt +++ b/requirements-plot.txt @@ -1,5 +1,5 @@ # Include all requirements to run the bot. -r requirements.txt -plotly==5.5.0 +plotly==5.6.0 From 03d4002be88be10ad8d7ebff6480b764fbbdab6f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Feb 2022 03:01:35 +0000 Subject: [PATCH 10/43] Bump pymdown-extensions from 9.1 to 9.2 Bumps [pymdown-extensions](https://github.com/facelessuser/pymdown-extensions) from 9.1 to 9.2. - [Release notes](https://github.com/facelessuser/pymdown-extensions/releases) - [Commits](https://github.com/facelessuser/pymdown-extensions/compare/9.1...9.2) --- updated-dependencies: - dependency-name: pymdown-extensions dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- docs/requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt index ad6d43f17..445e45723 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -1,4 +1,4 @@ mkdocs==1.2.3 mkdocs-material==8.1.10 mdx_truly_sane_lists==1.2 -pymdown-extensions==9.1 +pymdown-extensions==9.2 From 1674beed9121f6d19286530886c243949328468c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Feb 2022 03:01:43 +0000 Subject: [PATCH 11/43] Bump ccxt from 1.72.36 to 1.72.98 Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.72.36 to 1.72.98. - [Release notes](https://github.com/ccxt/ccxt/releases) - [Changelog](https://github.com/ccxt/ccxt/blob/master/exchanges.cfg) - [Commits](https://github.com/ccxt/ccxt/compare/1.72.36...1.72.98) --- updated-dependencies: - dependency-name: ccxt dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index ba20a169f..aaee3ca24 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ numpy==1.22.2 pandas==1.4.0 pandas-ta==0.3.14b -ccxt==1.72.36 +ccxt==1.72.98 # Pin cryptography for now due to rust build errors with piwheels cryptography==36.0.1 aiohttp==3.8.1 From b18e44bc43d2b0f5eabe1294a989a53f74acdecc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Feb 2022 03:01:46 +0000 Subject: [PATCH 12/43] Bump pytest from 7.0.0 to 7.0.1 Bumps [pytest](https://github.com/pytest-dev/pytest) from 7.0.0 to 7.0.1. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/7.0.0...7.0.1) --- updated-dependencies: - dependency-name: pytest dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 3fcdaab63..3ad19cdd3 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,7 +7,7 @@ coveralls==3.3.1 flake8==4.0.1 flake8-tidy-imports==4.6.0 mypy==0.931 -pytest==7.0.0 +pytest==7.0.1 pytest-asyncio==0.17.2 pytest-cov==3.0.0 pytest-mock==3.7.0 From 22036d69d83befb7f7d62e0e1d6e9a7a03e8c190 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Feb 2022 03:01:50 +0000 Subject: [PATCH 13/43] Bump mkdocs-material from 8.1.10 to 8.1.11 Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 8.1.10 to 8.1.11. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/8.1.10...8.1.11) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- docs/requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt index ad6d43f17..11e5bcc72 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -1,4 +1,4 @@ mkdocs==1.2.3 -mkdocs-material==8.1.10 +mkdocs-material==8.1.11 mdx_truly_sane_lists==1.2 pymdown-extensions==9.1 From 7f8e956b44fe38c423867e8e479aa99c40c6309c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Feb 2022 03:01:52 +0000 Subject: [PATCH 14/43] Bump types-requests from 2.27.8 to 2.27.9 Bumps [types-requests](https://github.com/python/typeshed) from 2.27.8 to 2.27.9. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-requests dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 3fcdaab63..05d2c0f87 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -22,7 +22,7 @@ nbconvert==6.4.1 # mypy types types-cachetools==4.2.9 types-filelock==3.2.5 -types-requests==2.27.8 +types-requests==2.27.9 types-tabulate==0.8.5 # Extensions to datetime library From 04c20afecebaf33bcedc27921292efab3a804f9b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Feb 2022 03:01:56 +0000 Subject: [PATCH 15/43] Bump nbconvert from 6.4.1 to 6.4.2 Bumps [nbconvert](https://github.com/jupyter/nbconvert) from 6.4.1 to 6.4.2. - [Release notes](https://github.com/jupyter/nbconvert/releases) - [Commits](https://github.com/jupyter/nbconvert/compare/6.4.1...6.4.2) --- updated-dependencies: - dependency-name: nbconvert dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 3fcdaab63..2c26b67ed 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -17,7 +17,7 @@ isort==5.10.1 time-machine==2.6.0 # Convert jupyter notebooks to markdown documents -nbconvert==6.4.1 +nbconvert==6.4.2 # mypy types types-cachetools==4.2.9 From 5062c17ac03872b1e36d668ce8f41d834db96964 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Feb 2022 03:02:07 +0000 Subject: [PATCH 16/43] Bump pandas from 1.4.0 to 1.4.1 Bumps [pandas](https://github.com/pandas-dev/pandas) from 1.4.0 to 1.4.1. - [Release notes](https://github.com/pandas-dev/pandas/releases) - [Changelog](https://github.com/pandas-dev/pandas/blob/main/RELEASE.md) - [Commits](https://github.com/pandas-dev/pandas/compare/v1.4.0...v1.4.1) --- updated-dependencies: - dependency-name: pandas dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index ba20a169f..a7d3a84b7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ numpy==1.22.2 -pandas==1.4.0 +pandas==1.4.1 pandas-ta==0.3.14b ccxt==1.72.36 From 5cc6c2afe1c4e012b5c998b9e485aa601e658c80 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Feb 2022 05:28:10 +0000 Subject: [PATCH 17/43] Bump pytest-asyncio from 0.17.2 to 0.18.1 Bumps [pytest-asyncio](https://github.com/pytest-dev/pytest-asyncio) from 0.17.2 to 0.18.1. - [Release notes](https://github.com/pytest-dev/pytest-asyncio/releases) - [Commits](https://github.com/pytest-dev/pytest-asyncio/compare/v0.17.2...v0.18.1) --- updated-dependencies: - dependency-name: pytest-asyncio dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 3ad19cdd3..72fea0828 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -8,7 +8,7 @@ flake8==4.0.1 flake8-tidy-imports==4.6.0 mypy==0.931 pytest==7.0.1 -pytest-asyncio==0.17.2 +pytest-asyncio==0.18.1 pytest-cov==3.0.0 pytest-mock==3.7.0 pytest-random-order==1.0.4 From acd7f26a9d14e75ffa4129d4cd6086992db63052 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 14 Feb 2022 19:57:51 +0100 Subject: [PATCH 18/43] update tc36 to properly cover #6261 --- tests/optimize/test_backtest_detail.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/optimize/test_backtest_detail.py b/tests/optimize/test_backtest_detail.py index 3164e11b9..c23fd8f44 100644 --- a/tests/optimize/test_backtest_detail.py +++ b/tests/optimize/test_backtest_detail.py @@ -568,11 +568,11 @@ tc35 = BTContainer(data=[ tc36 = BTContainer(data=[ # D O H L C V B S BT [0, 5000, 5050, 4950, 5000, 6172, 1, 0], - [1, 5000, 5500, 4951, 5000, 6172, 0, 0], # Enter and immediate ROI + [1, 5000, 5500, 4951, 4999, 6172, 0, 0], # Enter and immediate ROI [2, 4900, 5250, 4500, 5100, 6172, 0, 0], [3, 5100, 5100, 4650, 4750, 6172, 0, 0], [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], - stop_loss=-0.01, roi={"0": 0.10}, profit_perc=0.1, + stop_loss=-0.10, roi={"0": 0.01}, profit_perc=0.01, custom_entry_price=4952, trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=1)] ) From 30f6dbfc406453fd2783031b2a2817738c136327 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 14 Feb 2022 20:02:38 +0100 Subject: [PATCH 19/43] Attempt fix for #6261 --- freqtrade/optimize/backtesting.py | 14 +++++++++++++- tests/optimize/test_backtest_detail.py | 8 ++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 3f569649a..3126e1943 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -357,6 +357,15 @@ class Backtesting: # use Open rate if open_rate > calculated sell rate return sell_row[OPEN_IDX] + if ( + trade_dur == 0 + and trade.open_rate < sell_row[OPEN_IDX] # trade-open > open_rate + and sell_row[OPEN_IDX] > sell_row[CLOSE_IDX] # Red candle + ): + # ROI on opening candles with custom pricing can only + # trigger if the entry was at Open or lower. + # details: https: // github.com/freqtrade/freqtrade/issues/6261 + raise ValueError("Opening candle ROI on red candles.") # Use the maximum between close_rate and low as we # cannot sell outside of a candle. # Applies when a new ROI setting comes in place and the whole candle is above that. @@ -414,7 +423,10 @@ class Backtesting: trade.close_date = sell_candle_time trade_dur = int((trade.close_date_utc - trade.open_date_utc).total_seconds() // 60) - closerate = self._get_close_rate(sell_row, trade, sell, trade_dur) + try: + closerate = self._get_close_rate(sell_row, trade, sell, trade_dur) + except ValueError: + return None # call the custom exit price,with default value as previous closerate current_profit = trade.calc_profit_ratio(closerate) order_type = self.strategy.order_types['sell'] diff --git a/tests/optimize/test_backtest_detail.py b/tests/optimize/test_backtest_detail.py index c23fd8f44..5ad67d9a0 100644 --- a/tests/optimize/test_backtest_detail.py +++ b/tests/optimize/test_backtest_detail.py @@ -562,9 +562,9 @@ tc35 = BTContainer(data=[ ) # Test 36: Custom-entry-price around candle low -# Causes immediate ROI exit. This is currently expected behavior (#6261) -# https://github.com/freqtrade/freqtrade/issues/6261 -# But may change at a later point. +# Would cause immediate ROI exit, but since the trade was entered +# below open, we treat this as cheating, and delay the sell by 1 candle. +# details: https://github.com/freqtrade/freqtrade/issues/6261 tc36 = BTContainer(data=[ # D O H L C V B S BT [0, 5000, 5050, 4950, 5000, 6172, 1, 0], @@ -574,7 +574,7 @@ tc36 = BTContainer(data=[ [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], stop_loss=-0.10, roi={"0": 0.01}, profit_perc=0.01, custom_entry_price=4952, - trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=1)] + trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=2)] ) From 7b2e33b0bcecb7518bbfcf921f197ecfa6a14b27 Mon Sep 17 00:00:00 2001 From: Maik H Date: Mon, 14 Feb 2022 20:21:42 +0100 Subject: [PATCH 20/43] corrects typo --- docs/includes/pairlists.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/includes/pairlists.md b/docs/includes/pairlists.md index 031397719..cec5ceb19 100644 --- a/docs/includes/pairlists.md +++ b/docs/includes/pairlists.md @@ -246,7 +246,7 @@ On exchanges that deduct fees from the receiving currency (e.g. FTX) - this can The `low_price_ratio` setting removes pairs where a raise of 1 price unit (pip) is above the `low_price_ratio` ratio. This option is disabled by default, and will only apply if set to > 0. -For `PriceFiler` at least one of its `min_price`, `max_price` or `low_price_ratio` settings must be applied. +For `PriceFilter` at least one of its `min_price`, `max_price` or `low_price_ratio` settings must be applied. Calculation example: From 64b98989d2be20fe0fc85dc52929f63e6b4f9fce Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 15 Feb 2022 19:25:32 +0100 Subject: [PATCH 21/43] Update open candle ROI condition --- freqtrade/optimize/backtesting.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 3126e1943..b423771ca 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -359,12 +359,15 @@ class Backtesting: if ( trade_dur == 0 - and trade.open_rate < sell_row[OPEN_IDX] # trade-open > open_rate + # Red candle (for longs), TODO: green candle (for shorts) and sell_row[OPEN_IDX] > sell_row[CLOSE_IDX] # Red candle + and trade.open_rate < sell_row[OPEN_IDX] # trade-open below open_rate + and close_rate > sell_row[CLOSE_IDX] ): # ROI on opening candles with custom pricing can only # trigger if the entry was at Open or lower. # details: https: // github.com/freqtrade/freqtrade/issues/6261 + # If open_rate is < open, only allow sells below the close on red candles. raise ValueError("Opening candle ROI on red candles.") # Use the maximum between close_rate and low as we # cannot sell outside of a candle. From 3787b747ae60f21780f0a72a864db7aae5142687 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 15 Feb 2022 20:01:35 +0100 Subject: [PATCH 22/43] Simplify api schema by not using union types --- freqtrade/rpc/api_server/api_backtest.py | 7 +++++-- freqtrade/rpc/api_server/api_schemas.py | 4 ++-- freqtrade/rpc/rpc.py | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/freqtrade/rpc/api_server/api_backtest.py b/freqtrade/rpc/api_server/api_backtest.py index 97b7b7989..a008702a9 100644 --- a/freqtrade/rpc/api_server/api_backtest.py +++ b/freqtrade/rpc/api_server/api_backtest.py @@ -32,6 +32,10 @@ async def api_start_backtest(bt_settings: BacktestRequest, background_tasks: Bac for setting in settings.keys(): if settings[setting] is not None: btconfig[setting] = settings[setting] + try: + btconfig['stake_amount'] = float(btconfig['stake_amount']) + except ValueError: + pass # Force dry-run for backtesting btconfig['dry_run'] = True @@ -57,8 +61,7 @@ async def api_start_backtest(bt_settings: BacktestRequest, background_tasks: Bac ): from freqtrade.optimize.backtesting import Backtesting ApiServer._bt = Backtesting(btconfig) - if ApiServer._bt.timeframe_detail: - ApiServer._bt.load_bt_data_detail() + ApiServer._bt.load_bt_data_detail() else: ApiServer._bt.config = btconfig ApiServer._bt.init_backtest() diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py index ede5dcf0b..e22cf82b3 100644 --- a/freqtrade/rpc/api_server/api_schemas.py +++ b/freqtrade/rpc/api_server/api_schemas.py @@ -149,7 +149,7 @@ class ShowConfig(BaseModel): api_version: float dry_run: bool stake_currency: str - stake_amount: Union[float, str] + stake_amount: str available_capital: Optional[float] stake_currency_decimals: int max_open_trades: int @@ -366,7 +366,7 @@ class BacktestRequest(BaseModel): timeframe_detail: Optional[str] timerange: Optional[str] max_open_trades: Optional[int] - stake_amount: Optional[Union[float, str]] + stake_amount: Optional[str] enable_protections: bool dry_run_wallet: Optional[float] diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 377134542..5912a0ecd 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -112,7 +112,7 @@ class RPC: 'dry_run': config['dry_run'], 'stake_currency': config['stake_currency'], 'stake_currency_decimals': decimals_per_coin(config['stake_currency']), - 'stake_amount': config['stake_amount'], + 'stake_amount': str(config['stake_amount']), 'available_capital': config.get('available_capital'), 'max_open_trades': (config['max_open_trades'] if config['max_open_trades'] != float('inf') else -1), From 78a93b60526640c272fc725e3f37e4e76e6c9191 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 15 Feb 2022 20:15:03 +0100 Subject: [PATCH 23/43] noqa --- freqtrade/rpc/api_server/api_backtest.py | 1 + 1 file changed, 1 insertion(+) diff --git a/freqtrade/rpc/api_server/api_backtest.py b/freqtrade/rpc/api_server/api_backtest.py index a008702a9..8b86b8005 100644 --- a/freqtrade/rpc/api_server/api_backtest.py +++ b/freqtrade/rpc/api_server/api_backtest.py @@ -20,6 +20,7 @@ router = APIRouter() @router.post('/backtest', response_model=BacktestResponse, tags=['webserver', 'backtest']) +# flake8: noqa: C901 async def api_start_backtest(bt_settings: BacktestRequest, background_tasks: BackgroundTasks, config=Depends(get_config)): """Start backtesting if not done so already""" From b043697d701d9169a2e21760933d1861ebfc2dab Mon Sep 17 00:00:00 2001 From: Stefano Ariestasia Date: Wed, 16 Feb 2022 12:19:48 +0900 Subject: [PATCH 24/43] Update config_full.example.json --- config_examples/config_full.example.json | 1 + 1 file changed, 1 insertion(+) diff --git a/config_examples/config_full.example.json b/config_examples/config_full.example.json index 895a0af87..d675fb1a9 100644 --- a/config_examples/config_full.example.json +++ b/config_examples/config_full.example.json @@ -8,6 +8,7 @@ "amend_last_stake_amount": false, "last_stake_amount_min_ratio": 0.5, "dry_run": true, + "dry_run_wallet": 1000, "cancel_open_orders_on_exit": false, "timeframe": "5m", "trailing_stop": false, From e7bfb4fd5c22696fb87b1753a29f6c814c996ced Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 16 Feb 2022 13:42:39 +0100 Subject: [PATCH 25/43] Add test case for "sell below close" case --- tests/optimize/test_backtest_detail.py | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/tests/optimize/test_backtest_detail.py b/tests/optimize/test_backtest_detail.py index 5ad67d9a0..977563eeb 100644 --- a/tests/optimize/test_backtest_detail.py +++ b/tests/optimize/test_backtest_detail.py @@ -577,10 +577,24 @@ tc36 = BTContainer(data=[ trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=2)] ) - -# Test 37: Custom exit price below all candles -# Price adjusted to candle Low. +# Test 37: Custom-entry-price around candle low +# Would cause immediate ROI exit below close +# details: https://github.com/freqtrade/freqtrade/issues/6261 tc37 = BTContainer(data=[ + # D O H L C V B S BT + [0, 5000, 5050, 4950, 5000, 6172, 1, 0], + [1, 5400, 5500, 4951, 5100, 6172, 0, 0], # Enter and immediate ROI + [2, 4900, 5250, 4500, 5100, 6172, 0, 0], + [3, 5100, 5100, 4650, 4750, 6172, 0, 0], + [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], + stop_loss=-0.10, roi={"0": 0.01}, profit_perc=0.01, + custom_entry_price=4952, + trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=1)] +) + +# Test 38: Custom exit price below all candles +# Price adjusted to candle Low. +tc38 = BTContainer(data=[ # D O H L C V B S BT [0, 5000, 5050, 4950, 5000, 6172, 1, 0], [1, 5000, 5500, 4951, 5000, 6172, 0, 0], @@ -593,9 +607,9 @@ tc37 = BTContainer(data=[ trades=[BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=3)] ) -# Test 38: Custom exit price above all candles +# Test 39: Custom exit price above all candles # causes sell signal timeout -tc38 = BTContainer(data=[ +tc39 = BTContainer(data=[ # D O H L C V B S BT [0, 5000, 5050, 4950, 5000, 6172, 1, 0], [1, 5000, 5500, 4951, 5000, 6172, 0, 0], @@ -649,6 +663,7 @@ TESTS = [ tc36, tc37, tc38, + tc39, ] From e60553b8f7b6400cddba5fafd04419ee5241f069 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 16 Feb 2022 19:21:04 +0100 Subject: [PATCH 26/43] Add max_entry_position hyperopt to docs closes #6356 --- docs/hyperopt.md | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/docs/hyperopt.md b/docs/hyperopt.md index 27d5a8761..19d8cd692 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -508,6 +508,46 @@ class MyAwesomeStrategy(IStrategy): You will then obviously also change potential interesting entries to parameters to allow hyper-optimization. +### Optimizing `max_entry_position_adjustment` + +While `max_entry_position_adjustment` is not a separate space, it can still be used in hyperopt by using the property approach shown above. + +``` python +from pandas import DataFrame +from functools import reduce + +import talib.abstract as ta + +from freqtrade.strategy import (BooleanParameter, CategoricalParameter, DecimalParameter, + IStrategy, IntParameter) +import freqtrade.vendor.qtpylib.indicators as qtpylib + +class MyAwesomeStrategy(IStrategy): + stoploss = -0.05 + timeframe = '15m' + + # Define the parameter spaces + max_epa = CategoricalParameter([-1, 0, 1, 3, 5, 10], default=1, space="buy", optimize=True) + + @property + def max_entry_position_adjustment(self): + return self.max_epa.value + + + def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + # ... +``` + +??? Tip "Using `IntParameter`" + You can also use the `IntParameter` for this optimization, but you must explicitly return an integer: + ``` python + max_epa = IntParameter(-1, 10, default=1, space="buy", optimize=True) + + @property + def max_entry_position_adjustment(self): + return int(self.max_epa.value) + ``` + ## Loss-functions Each hyperparameter tuning requires a target. This is usually defined as a loss function (sometimes also called objective function), which should decrease for more desirable results, and increase for bad results. From eb88c0f71b2dda0f5aab31f2fcaa7d1d95d0f0bc Mon Sep 17 00:00:00 2001 From: Kavinkumar <33546454+mkavinkumar1@users.noreply.github.com> Date: Fri, 18 Feb 2022 19:25:56 +0530 Subject: [PATCH 27/43] fixed stake amount --- tests/rpc/test_rpc_telegram.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 67a6c72fe..f39c627f1 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -2031,7 +2031,7 @@ def test_send_msg_buy_notification_no_fiat(default_conf, mocker) -> None: 'pair': 'ETH/BTC', 'limit': 1.099e-05, 'order_type': 'limit', - 'stake_amount': 0.001, + 'stake_amount': 0.015, 'stake_amount_fiat': 0.0, 'stake_currency': 'BTC', 'fiat_currency': None, @@ -2044,7 +2044,7 @@ def test_send_msg_buy_notification_no_fiat(default_conf, mocker) -> None: '*Amount:* `1333.33333333`\n' '*Open Rate:* `0.00001099`\n' '*Current Rate:* `0.00001099`\n' - '*Total:* `(0.00100000 BTC)`') + '*Total:* `(0.01500000 BTC)`') def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None: From 95d4a11bb1564bfc5a555f9ae02fedf6af6f8a6a Mon Sep 17 00:00:00 2001 From: Kavinkumar <33546454+mkavinkumar1@users.noreply.github.com> Date: Fri, 18 Feb 2022 19:32:37 +0530 Subject: [PATCH 28/43] add precision --- tests/rpc/test_rpc_telegram.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index f39c627f1..e42d6bfa7 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -2031,7 +2031,7 @@ def test_send_msg_buy_notification_no_fiat(default_conf, mocker) -> None: 'pair': 'ETH/BTC', 'limit': 1.099e-05, 'order_type': 'limit', - 'stake_amount': 0.015, + 'stake_amount': 0.01465333, 'stake_amount_fiat': 0.0, 'stake_currency': 'BTC', 'fiat_currency': None, @@ -2044,7 +2044,7 @@ def test_send_msg_buy_notification_no_fiat(default_conf, mocker) -> None: '*Amount:* `1333.33333333`\n' '*Open Rate:* `0.00001099`\n' '*Current Rate:* `0.00001099`\n' - '*Total:* `(0.01500000 BTC)`') + '*Total:* `(0.01465333 BTC)`') def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None: From 60d1e7fc6578e57ebd27ad05b37e4de63e1ed20f Mon Sep 17 00:00:00 2001 From: Kavinkumar <33546454+mkavinkumar1@users.noreply.github.com> Date: Fri, 18 Feb 2022 19:47:45 +0530 Subject: [PATCH 29/43] fix stake amt --- tests/rpc/test_rpc_telegram.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index e42d6bfa7..4d00095e4 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -1734,7 +1734,7 @@ def test_send_msg_buy_notification(default_conf, mocker, caplog) -> None: 'pair': 'ETH/BTC', 'limit': 1.099e-05, 'order_type': 'limit', - 'stake_amount': 0.001, + 'stake_amount': 0.01465333, 'stake_amount_fiat': 0.0, 'stake_currency': 'BTC', 'fiat_currency': 'USD', @@ -1751,7 +1751,7 @@ def test_send_msg_buy_notification(default_conf, mocker, caplog) -> None: '*Amount:* `1333.33333333`\n' \ '*Open Rate:* `0.00001099`\n' \ '*Current Rate:* `0.00001099`\n' \ - '*Total:* `(0.00100000 BTC, 12.345 USD)`' + '*Total:* `(0.01465333 BTC, 180.895 USD)`' freqtradebot.config['telegram']['notification_settings'] = {'buy': 'off'} caplog.clear() @@ -1825,7 +1825,7 @@ def test_send_msg_buy_fill_notification(default_conf, mocker) -> None: 'buy_tag': 'buy_signal_01', 'exchange': 'Binance', 'pair': 'ETH/BTC', - 'stake_amount': 0.001, + 'stake_amount': 0.01465333, # 'stake_amount_fiat': 0.0, 'stake_currency': 'BTC', 'fiat_currency': 'USD', @@ -1839,7 +1839,7 @@ def test_send_msg_buy_fill_notification(default_conf, mocker) -> None: '*Buy Tag:* `buy_signal_01`\n' \ '*Amount:* `1333.33333333`\n' \ '*Open Rate:* `0.00001099`\n' \ - '*Total:* `(0.00100000 BTC, 12.345 USD)`' + '*Total:* `(0.01465333 BTC, 180.895 USD)`' def test_send_msg_sell_notification(default_conf, mocker) -> None: From 0bbbe2e96c5e58413a6611db7d8c967514727fe2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 19 Feb 2022 06:36:23 +0100 Subject: [PATCH 30/43] Add test for #6429 --- tests/test_freqtradebot.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 4bbf26362..08d98b42d 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -2436,6 +2436,9 @@ def test_handle_cancel_enter(mocker, caplog, default_conf_usdt, limit_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 log_has_re(r"Order .* for .* not cancelled.", caplog) + # min_pair_stake empty should not crash + mocker.patch('freqtrade.exchange.Exchange.get_min_pair_stake_amount', return_value=None) + assert not freqtrade.handle_cancel_enter(trade, limit_buy_order_usdt, reason) @pytest.mark.parametrize("limit_buy_order_canceled_empty", ['binance', 'ftx', 'kraken', 'bittrex'], From 3785f04be7c8bbeb01e9f849ad194ac77dfeeeeb Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 19 Feb 2022 06:38:11 +0100 Subject: [PATCH 31/43] Handle empty min stake amount as observed on FTX (closes #6429) --- freqtrade/freqtradebot.py | 4 ++-- freqtrade/persistence/models.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index fce85baa3..5f2b72e1e 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -1021,12 +1021,12 @@ class FreqtradeBot(LoggingMixin): # Cancelled orders may have the status of 'canceled' or 'closed' if order['status'] not in constants.NON_OPEN_EXCHANGE_STATES: - filled_val = order.get('filled', 0.0) or 0.0 + filled_val: float = order.get('filled', 0.0) or 0.0 filled_stake = filled_val * trade.open_rate minstake = self.exchange.get_min_pair_stake_amount( trade.pair, trade.open_rate, self.strategy.stoploss) - if filled_val > 0 and filled_stake < minstake: + if filled_val > 0 and minstake 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.") diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index 5f2db1050..b8ea5848f 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -799,11 +799,11 @@ class Trade(_DECL_BASE, LocalTrade): fee_close = Column(Float, nullable=False, default=0.0) fee_close_cost = Column(Float, nullable=True) fee_close_currency = Column(String(25), nullable=True) - open_rate = Column(Float) + open_rate: float = Column(Float) open_rate_requested = Column(Float) # open_trade_value - calculated via _calc_open_trade_value open_trade_value = Column(Float) - close_rate = Column(Float) + close_rate: Optional[float] = Column(Float) close_rate_requested = Column(Float) close_profit = Column(Float) close_profit_abs = Column(Float) From a32aed2225544073eb020d91ce1806e04b2d4ce1 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 19 Feb 2022 10:07:32 +0100 Subject: [PATCH 32/43] Update FTX stoploss code to avoid exception for stoploss-market orders closes #6430, closes #6392 --- freqtrade/exchange/ftx.py | 19 +++++++++++-------- tests/exchange/test_ftx.py | 21 +++++++++++++++++++-- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/freqtrade/exchange/ftx.py b/freqtrade/exchange/ftx.py index e9eb2fe19..a8bf9abac 100644 --- a/freqtrade/exchange/ftx.py +++ b/freqtrade/exchange/ftx.py @@ -106,15 +106,18 @@ class Ftx(Exchange): if order[0].get('status') == 'closed': # Trigger order was triggered ... real_order_id = order[0].get('info', {}).get('orderId') + # OrderId may be None for stoploss-market orders + # But contains "average" in these cases. + if real_order_id: + order1 = self._api.fetch_order(real_order_id, pair) + self._log_exchange_response('fetch_stoploss_order1', order1) + # Fake type to stop - as this was really a stop order. + order1['id_stop'] = order1['id'] + order1['id'] = order_id + order1['type'] = 'stop' + order1['status_stop'] = 'triggered' + return order1 - order1 = self._api.fetch_order(real_order_id, pair) - self._log_exchange_response('fetch_stoploss_order1', order1) - # Fake type to stop - as this was really a stop order. - order1['id_stop'] = order1['id'] - order1['id'] = order_id - order1['type'] = 'stop' - order1['status_stop'] = 'triggered' - return order1 return order[0] else: raise InvalidOrderException(f"Could not get stoploss order for id {order_id}") diff --git a/tests/exchange/test_ftx.py b/tests/exchange/test_ftx.py index 3794bb79c..c2fb90c9d 100644 --- a/tests/exchange/test_ftx.py +++ b/tests/exchange/test_ftx.py @@ -125,7 +125,7 @@ def test_stoploss_adjust_ftx(mocker, default_conf): assert not exchange.stoploss_adjust(1501, order) -def test_fetch_stoploss_order(default_conf, mocker, limit_sell_order): +def test_fetch_stoploss_order_ftx(default_conf, mocker, limit_sell_order): default_conf['dry_run'] = True order = MagicMock() order.myid = 123 @@ -147,9 +147,15 @@ def test_fetch_stoploss_order(default_conf, mocker, limit_sell_order): with pytest.raises(InvalidOrderException, match=r"Could not get stoploss order for id X"): exchange.fetch_stoploss_order('X', 'TKN/BTC')['status'] - api_mock.fetch_orders = MagicMock(return_value=[{'id': 'X', 'status': 'closed'}]) + # stoploss Limit order + api_mock.fetch_orders = MagicMock(return_value=[ + {'id': 'X', 'status': 'closed', + 'info': { + 'orderId': 'mocked_limit_sell', + }}]) api_mock.fetch_order = MagicMock(return_value=limit_sell_order) + # No orderId field - no call to fetch_order resp = exchange.fetch_stoploss_order('X', 'TKN/BTC') assert resp assert api_mock.fetch_order.call_count == 1 @@ -158,6 +164,17 @@ def test_fetch_stoploss_order(default_conf, mocker, limit_sell_order): assert resp['type'] == 'stop' assert resp['status_stop'] == 'triggered' + # Stoploss market order + # Contains no new Order, but "average" instead + order = {'id': 'X', 'status': 'closed', 'info': {'orderId': None}, 'average': 0.254} + api_mock.fetch_orders = MagicMock(return_value=[order]) + api_mock.fetch_order.reset_mock() + resp = exchange.fetch_stoploss_order('X', 'TKN/BTC') + assert resp + # fetch_order not called (no regular order ID) + assert api_mock.fetch_order.call_count == 0 + assert order == order + with pytest.raises(InvalidOrderException): api_mock.fetch_orders = MagicMock(side_effect=ccxt.InvalidOrder("Order not found")) exchange = get_patched_exchange(mocker, default_conf, api_mock, id='ftx') From a7a25bb28514426531702f260edaeb11b0c9d994 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 19 Feb 2022 16:35:17 +0100 Subject: [PATCH 33/43] Update "round coin value" to trim trailing zeros --- freqtrade/misc.py | 13 +++++++++---- tests/test_misc.py | 7 +++++-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/freqtrade/misc.py b/freqtrade/misc.py index 2a27f1660..133014f39 100644 --- a/freqtrade/misc.py +++ b/freqtrade/misc.py @@ -29,18 +29,23 @@ def decimals_per_coin(coin: str): return DECIMALS_PER_COIN.get(coin, DECIMAL_PER_COIN_FALLBACK) -def round_coin_value(value: float, coin: str, show_coin_name=True) -> str: +def round_coin_value( + value: float, coin: str, show_coin_name=True, keep_trailing_zeros=False) -> str: """ Get price value for this coin :param value: Value to be printed :param coin: Which coin are we printing the price / value for :param show_coin_name: Return string in format: "222.22 USDT" or "222.22" + :param keep_trailing_zeros: Keep trailing zeros "222.200" vs. "222.2" :return: Formatted / rounded value (with or without coin name) """ + val = f"{value:.{decimals_per_coin(coin)}f}" + if not keep_trailing_zeros: + val = val.rstrip('0').rstrip('.') if show_coin_name: - return f"{value:.{decimals_per_coin(coin)}f} {coin}" - else: - return f"{value:.{decimals_per_coin(coin)}f}" + val = f"{val} {coin}" + + return val def shorten_date(_date: str) -> str: diff --git a/tests/test_misc.py b/tests/test_misc.py index 21a00f3be..4fd5338ad 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -21,16 +21,19 @@ def test_decimals_per_coin(): def test_round_coin_value(): assert round_coin_value(222.222222, 'USDT') == '222.222 USDT' - assert round_coin_value(222.2, 'USDT') == '222.200 USDT' + assert round_coin_value(222.2, 'USDT', keep_trailing_zeros=True) == '222.200 USDT' + assert round_coin_value(222.2, 'USDT') == '222.2 USDT' assert round_coin_value(222.12745, 'EUR') == '222.127 EUR' assert round_coin_value(0.1274512123, 'BTC') == '0.12745121 BTC' assert round_coin_value(0.1274512123, 'ETH') == '0.12745 ETH' assert round_coin_value(222.222222, 'USDT', False) == '222.222' - assert round_coin_value(222.2, 'USDT', False) == '222.200' + assert round_coin_value(222.2, 'USDT', False) == '222.2' + assert round_coin_value(222.00, 'USDT', False) == '222' assert round_coin_value(222.12745, 'EUR', False) == '222.127' assert round_coin_value(0.1274512123, 'BTC', False) == '0.12745121' assert round_coin_value(0.1274512123, 'ETH', False) == '0.12745' + assert round_coin_value(222.2, 'USDT', False, True) == '222.200' def test_shorten_date() -> None: From d610b6305de54e0cabb60350681313d00324d988 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 19 Feb 2022 16:39:47 +0100 Subject: [PATCH 34/43] Improve /balance output by removing trailing zeros --- freqtrade/optimize/hyperopt_tools.py | 4 ++-- freqtrade/rpc/telegram.py | 13 +++++++------ tests/rpc/test_rpc_telegram.py | 6 +++--- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/freqtrade/optimize/hyperopt_tools.py b/freqtrade/optimize/hyperopt_tools.py index 61a10c32b..8c84f772a 100755 --- a/freqtrade/optimize/hyperopt_tools.py +++ b/freqtrade/optimize/hyperopt_tools.py @@ -373,7 +373,7 @@ class HyperoptTools(): trials[f"Max Drawdown{' (Acct)' if has_account_drawdown else ''}"] = trials.apply( lambda x: "{} {}".format( - round_coin_value(x['max_drawdown_abs'], stake_currency), + round_coin_value(x['max_drawdown_abs'], stake_currency, keep_trailing_zeros=True), (f"({x['max_drawdown_account']:,.2%})" if has_account_drawdown else f"({x['max_drawdown']:,.2%})" @@ -388,7 +388,7 @@ class HyperoptTools(): trials['Profit'] = trials.apply( lambda x: '{} {}'.format( - round_coin_value(x['Total profit'], stake_currency), + round_coin_value(x['Total profit'], stake_currency, keep_trailing_zeros=True), f"({x['Profit']:,.2%})".rjust(10, ' ') ).rjust(25+len(stake_currency)) if x['Total profit'] != 0.0 else '--'.rjust(25+len(stake_currency)), diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 0a634ffae..da613fab8 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -790,12 +790,13 @@ class Telegram(RPCHandler): output = '' if self._config['dry_run']: output += "*Warning:* Simulated balances in Dry Mode.\n" - - output += ("Starting capital: " - f"`{result['starting_capital']}` {self._config['stake_currency']}" - ) - output += (f" `{result['starting_capital_fiat']}` " - f"{self._config['fiat_display_currency']}.\n" + starting_cap = round_coin_value( + result['starting_capital'], self._config['stake_currency']) + output += f"Starting capital: `{starting_cap}`" + starting_cap_fiat = round_coin_value( + result['starting_capital_fiat'], self._config['fiat_display_currency'] + ) if result['starting_capital_fiat'] > 0 else '' + output += (f" `, {starting_cap_fiat}`.\n" ) if result['starting_capital_fiat'] > 0 else '.\n' total_dust_balance = 0 diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 4d00095e4..640f9305c 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -770,7 +770,7 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee, assert 'No closed trade' in msg_mock.call_args_list[-1][0][0] assert '*ROI:* All trades' in msg_mock.call_args_list[-1][0][0] mocker.patch('freqtrade.wallets.Wallets.get_starting_balance', return_value=0.01) - assert ('∙ `-0.00000500 BTC (-0.50%) (-0.0 \N{GREEK CAPITAL LETTER SIGMA}%)`' + assert ('∙ `-0.000005 BTC (-0.50%) (-0.0 \N{GREEK CAPITAL LETTER SIGMA}%)`' in msg_mock.call_args_list[-1][0][0]) msg_mock.reset_mock() @@ -845,7 +845,7 @@ def test_telegram_balance_handle(default_conf, update, mocker, rpc_balance, tick assert '*XRP:*' not in result assert 'Balance:' in result assert 'Est. BTC:' in result - assert 'BTC: 12.00000000' in result + assert 'BTC: 12' in result assert "*3 Other Currencies (< 0.0001 BTC):*" in result assert 'BTC: 0.00000309' in result @@ -874,7 +874,7 @@ def test_balance_handle_empty_response_dry(default_conf, update, mocker) -> None result = msg_mock.call_args_list[0][0][0] assert msg_mock.call_count == 1 assert "*Warning:* Simulated balances in Dry Mode." in result - assert "Starting capital: `1000` BTC" in result + assert "Starting capital: `1000 BTC`" in result def test_balance_handle_too_large_response(default_conf, update, mocker) -> None: From 21b5f56f7d031a45b6ffb79ef4651c7cb02bbf81 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Feb 2022 03:01:39 +0000 Subject: [PATCH 35/43] Bump types-requests from 2.27.9 to 2.27.10 Bumps [types-requests](https://github.com/python/typeshed) from 2.27.9 to 2.27.10. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-requests dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 3a2fa71e0..c52032a60 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -22,7 +22,7 @@ nbconvert==6.4.2 # mypy types types-cachetools==4.2.9 types-filelock==3.2.5 -types-requests==2.27.9 +types-requests==2.27.10 types-tabulate==0.8.5 # Extensions to datetime library From d1cded3532e96c6e76703f4e73c8055cb3b0b4f7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Feb 2022 03:01:45 +0000 Subject: [PATCH 36/43] Bump filelock from 3.4.2 to 3.6.0 Bumps [filelock](https://github.com/tox-dev/py-filelock) from 3.4.2 to 3.6.0. - [Release notes](https://github.com/tox-dev/py-filelock/releases) - [Changelog](https://github.com/tox-dev/py-filelock/blob/main/docs/changelog.rst) - [Commits](https://github.com/tox-dev/py-filelock/compare/3.4.2...3.6.0) --- updated-dependencies: - dependency-name: filelock dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-hyperopt.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-hyperopt.txt b/requirements-hyperopt.txt index e4698918a..aeb7be035 100644 --- a/requirements-hyperopt.txt +++ b/requirements-hyperopt.txt @@ -5,6 +5,6 @@ scipy==1.8.0 scikit-learn==1.0.2 scikit-optimize==0.9.0 -filelock==3.4.2 +filelock==3.6.0 joblib==1.1.0 progressbar2==4.0.0 From dc8e9bab44c94c971bf8409082b54d95981298f8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Feb 2022 03:01:51 +0000 Subject: [PATCH 37/43] Bump fastapi from 0.73.0 to 0.74.0 Bumps [fastapi](https://github.com/tiangolo/fastapi) from 0.73.0 to 0.74.0. - [Release notes](https://github.com/tiangolo/fastapi/releases) - [Commits](https://github.com/tiangolo/fastapi/compare/0.73.0...0.74.0) --- updated-dependencies: - dependency-name: fastapi dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index eaf257ca8..5b4be4f37 100644 --- a/requirements.txt +++ b/requirements.txt @@ -31,7 +31,7 @@ python-rapidjson==1.5 sdnotify==0.3.2 # API Server -fastapi==0.73.0 +fastapi==0.74.0 uvicorn==0.17.4 pyjwt==2.3.0 aiofiles==0.8.0 From 317487fefc8cea707d1767466a032af40929ed72 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Feb 2022 03:01:58 +0000 Subject: [PATCH 38/43] Bump ccxt from 1.72.98 to 1.73.70 Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.72.98 to 1.73.70. - [Release notes](https://github.com/ccxt/ccxt/releases) - [Changelog](https://github.com/ccxt/ccxt/blob/master/exchanges.cfg) - [Commits](https://github.com/ccxt/ccxt/compare/1.72.98...1.73.70) --- updated-dependencies: - dependency-name: ccxt dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index eaf257ca8..b396b10ac 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ numpy==1.22.2 pandas==1.4.1 pandas-ta==0.3.14b -ccxt==1.72.98 +ccxt==1.73.70 # Pin cryptography for now due to rust build errors with piwheels cryptography==36.0.1 aiohttp==3.8.1 From d354f1f84c22addbba79a2f39290f91ab447b932 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Feb 2022 03:02:06 +0000 Subject: [PATCH 39/43] Bump mkdocs-material from 8.1.11 to 8.2.1 Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 8.1.11 to 8.2.1. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/8.1.11...8.2.1) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- docs/requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt index d3772ebd7..3e7fa2044 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -1,4 +1,4 @@ mkdocs==1.2.3 -mkdocs-material==8.1.11 +mkdocs-material==8.2.1 mdx_truly_sane_lists==1.2 pymdown-extensions==9.2 From b9a99bd0b73a25ec59f3bf696a406fe05b2b5386 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Feb 2022 05:28:53 +0000 Subject: [PATCH 40/43] Bump python-rapidjson from 1.5 to 1.6 Bumps [python-rapidjson](https://github.com/python-rapidjson/python-rapidjson) from 1.5 to 1.6. - [Release notes](https://github.com/python-rapidjson/python-rapidjson/releases) - [Changelog](https://github.com/python-rapidjson/python-rapidjson/blob/master/CHANGES.rst) - [Commits](https://github.com/python-rapidjson/python-rapidjson/compare/v1.5...v1.6) --- updated-dependencies: - dependency-name: python-rapidjson dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 5b4be4f37..7f0c3cb6b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -25,7 +25,7 @@ blosc==1.10.6 py_find_1st==1.1.5 # Load ticker files 30% faster -python-rapidjson==1.5 +python-rapidjson==1.6 # Notify systemd sdnotify==0.3.2 From 02ce0dc02ef85db0485af262f81bce768c89091a Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 22 Feb 2022 19:31:58 +0100 Subject: [PATCH 41/43] Set journal mode to wal for sqlite databases closes #6353 --- freqtrade/persistence/migrations.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/freqtrade/persistence/migrations.py b/freqtrade/persistence/migrations.py index 60c0eb5f9..5817c5a97 100644 --- a/freqtrade/persistence/migrations.py +++ b/freqtrade/persistence/migrations.py @@ -186,6 +186,13 @@ def migrate_orders_table(engine, table_back_name: str, cols: List): """)) +def set_sqlite_to_wal(engine): + if engine.name == 'sqlite' and str(engine.url) != 'sqlite://': + # Set Mode to + with engine.begin() as connection: + connection.execute(text("PRAGMA journal_mode=wal")) + + def check_migrate(engine, decl_base, previous_tables) -> None: """ Checks if migration is necessary and migrates if necessary @@ -212,3 +219,4 @@ def check_migrate(engine, decl_base, previous_tables) -> None: if 'orders' not in previous_tables and 'trades' in previous_tables: logger.info('Moving open orders to Orders table.') migrate_open_orders_to_trades(engine) + set_sqlite_to_wal(engine) From 1f9ed0beff5256bbc881d4df2989381fb36f563f Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 22 Feb 2022 19:39:55 +0100 Subject: [PATCH 42/43] Add test for wal mode --- freqtrade/persistence/models.py | 3 +++ tests/test_persistence.py | 11 ++++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index b8ea5848f..5eddb7b3d 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -39,6 +39,9 @@ def init_db(db_url: str, clean_open_orders: bool = False) -> None: """ kwargs = {} + if db_url == 'sqlite:///': + raise OperationalException( + f'Bad db-url {db_url}. For in-memory database, please use `sqlite://`.') if db_url == 'sqlite://': kwargs.update({ 'poolclass': StaticPool, diff --git a/tests/test_persistence.py b/tests/test_persistence.py index b8f7a3336..b2dccc75c 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -33,13 +33,18 @@ def test_init_custom_db_url(default_conf, tmpdir): init_db(default_conf['db_url'], default_conf['dry_run']) assert Path(filename).is_file() + r = Trade._session.execute(text("PRAGMA journal_mode")) + assert r.first() == ('wal',) -def test_init_invalid_db_url(default_conf): +def test_init_invalid_db_url(): # Update path to a value other than default, but still in-memory - default_conf.update({'db_url': 'unknown:///some.url'}) with pytest.raises(OperationalException, match=r'.*no valid database URL*'): - init_db(default_conf['db_url'], default_conf['dry_run']) + init_db('unknown:///some.url', True) + + with pytest.raises(OperationalException, match=r'Bad db-url.*For in-memory database, pl.*'): + init_db('sqlite:///', True) + def test_init_prod_db(default_conf, mocker): From 5a4f30d1bda54af561a9fc089b9f4a7a1ee0181a Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 22 Feb 2022 20:07:41 +0100 Subject: [PATCH 43/43] Don't specially handle empty results. --- freqtrade/rpc/rpc.py | 5 ----- tests/rpc/test_rpc_telegram.py | 2 +- tests/test_persistence.py | 1 - 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 5912a0ecd..7a602978e 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -599,11 +599,6 @@ class RPC: 'est_stake': est_stake or 0, 'stake': stake_currency, }) - if total == 0.0: - if self._freqtrade.config['dry_run']: - raise RPCException('Running in Dry Run, balances are not available.') - else: - raise RPCException('All balances are zero.') value = self._fiat_converter.convert_amount( total, stake_currency, fiat_display_currency) if self._fiat_converter else 0 diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 640f9305c..353aa959f 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -861,7 +861,7 @@ def test_balance_handle_empty_response(default_conf, update, mocker) -> None: telegram._balance(update=update, context=MagicMock()) result = msg_mock.call_args_list[0][0][0] assert msg_mock.call_count == 1 - assert 'All balances are zero.' in result + assert 'Starting capital: `0 BTC' in result def test_balance_handle_empty_response_dry(default_conf, update, mocker) -> None: diff --git a/tests/test_persistence.py b/tests/test_persistence.py index b2dccc75c..f695aab8b 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -46,7 +46,6 @@ def test_init_invalid_db_url(): init_db('sqlite:///', True) - def test_init_prod_db(default_conf, mocker): default_conf.update({'dry_run': False}) default_conf.update({'db_url': constants.DEFAULT_DB_PROD_URL})