From 092a9ff5dbe183124c6bf91094cf5a97b7b7fc30 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 9 Jan 2025 18:06:58 +0100 Subject: [PATCH 1/4] chore: fix error if precision.price is None --- freqtrade/exchange/exchange.py | 1 + 1 file changed, 1 insertion(+) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 494949439b8..67c4cc70907 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -548,6 +548,7 @@ def market_is_tradable(self, market: dict[str, Any]) -> bool: and ( self.precisionMode != TICK_SIZE # Too low precision will falsify calculations + or market.get("precision", {}).get("price") is None or market.get("precision", {}).get("price") > 1e-11 ) and ( From 75fb2a0c86378f12f35d9e9a9bd860de7ebbcf9c Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 9 Jan 2025 18:21:51 +0100 Subject: [PATCH 2/4] fix: catch error when getting the last candle fails It's unknown when this actually happens - but this should be the safer bet than crashing completely. closes #11213 --- freqtrade/strategy/interface.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 73d3acfa957..77fe2a84a2a 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -1160,8 +1160,12 @@ def get_latest_candle( logger.warning(f"Empty candle (OHLCV) data for pair {pair}") return None, None - latest_date_pd = dataframe["date"].max() - latest = dataframe.loc[dataframe["date"] == latest_date_pd].iloc[-1] + try: + latest_date_pd = dataframe["date"].max() + latest = dataframe.loc[dataframe["date"] == latest_date_pd].iloc[-1] + except Exception as e: + logger.warning(f"Unable to get latest candle (OHLCV) data for pair {pair} - {e}") + return None, None # Explicitly convert to datetime object to ensure the below comparison does not fail latest_date: datetime = latest_date_pd.to_pydatetime() From e5d508a50789377bb95bfad3858bdbfbff7397f9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 9 Jan 2025 20:00:40 +0100 Subject: [PATCH 3/4] docs: update adjust-entry docs about partial fills These are canceled and NOT replaced - this needs to be pointed out. closes #11212 --- docs/strategy-callbacks.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index 997e90571f3..0bd0e154914 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -950,7 +950,8 @@ Returning any other price will cancel the existing order, and replace it with a The trade open-date (`trade.open_date_utc`) will remain at the time of the very first order placed. Please make sure to be aware of this - and eventually adjust your logic in other callbacks to account for this, and use the date of the first filled order instead. -If the cancellation of the original order fails, then the order will not be replaced - though the order will most likely have been canceled on exchange. Having this happen on initial entries will result in the deletion of the order, while on position adjustment orders, it'll result in the trade size remaining as is. +If the cancellation of the original order fails, then the order will not be replaced - though the order will most likely have been canceled on exchange. Having this happen on initial entries will result in the deletion of the order, while on position adjustment orders, it'll result in the trade size remaining as is. +If the order has been partially filled, the order will not be replaced. You can however use [`adjust_trade_position()`](#adjust-trade-position) to adjust the trade size to the full, expected position size, should this be necessary / desired. !!! Warning "Regular timeout" Entry `unfilledtimeout` mechanism (as well as `check_entry_timeout()`) takes precedence over this. From 711e680198269af331e280f30bd0be219aca642d Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 9 Jan 2025 20:04:57 +0100 Subject: [PATCH 4/4] chore: improve log on adjust_entry_price cancels --- freqtrade/freqtradebot.py | 2 +- tests/freqtradebot/test_freqtradebot.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 4327c80a53c..6212ad8550d 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -1695,7 +1695,7 @@ def replace_order(self, order: CcxtOrder, order_obj: Order | None, trade: Trade) ) if not res: self.replace_order_failed( - trade, f"Could not cancel order for {trade}, therefore not replacing." + trade, f"Could not fully cancel order for {trade}, therefore not replacing." ) return if adjusted_entry_price: diff --git a/tests/freqtradebot/test_freqtradebot.py b/tests/freqtradebot/test_freqtradebot.py index 7a30713bc5d..5cfc5d70152 100644 --- a/tests/freqtradebot/test_freqtradebot.py +++ b/tests/freqtradebot/test_freqtradebot.py @@ -2053,7 +2053,7 @@ def test_adjust_entry_replace_fail( assert len(trades) == 0 assert len(Order.session.scalars(select(Order)).all()) == 0 assert fetch_order_mock.call_count == 4 - assert log_has_re(r"Could not cancel order.*, therefore not replacing\.", caplog) + assert log_has_re(r"Could not fully cancel order.*, therefore not replacing\.", caplog) # Entry adjustment is called assert freqtrade.strategy.adjust_entry_price.call_count == 1