From cf96ad1d1bb3f81aef1197e252fb056d8c96f2c7 Mon Sep 17 00:00:00 2001 From: axel Date: Sat, 16 Sep 2023 02:32:03 -0400 Subject: [PATCH 01/12] add trade param to custom entry price in interface, bot, backtesting, exemples --- docs/strategy-callbacks.md | 2 +- docs/strategy_migration.md | 4 ++-- freqtrade/freqtradebot.py | 2 +- freqtrade/optimize/backtesting.py | 2 +- freqtrade/strategy/interface.py | 3 ++- tests/strategy/strats/strategy_test_v3_custom_entry_price.py | 4 +++- 6 files changed, 10 insertions(+), 7 deletions(-) diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index 6c86595d3..a818315ce 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -520,7 +520,7 @@ class AwesomeStrategy(IStrategy): # ... populate_* methods - def custom_entry_price(self, pair: str, current_time: datetime, proposed_rate: float, + def custom_entry_price(self, pair: str, trade: 'Trade', current_time: datetime, proposed_rate: float, entry_tag: Optional[str], side: str, **kwargs) -> float: dataframe, last_updated = self.dp.get_analyzed_dataframe(pair=pair, diff --git a/docs/strategy_migration.md b/docs/strategy_migration.md index d4d5f0068..aaa655473 100644 --- a/docs/strategy_migration.md +++ b/docs/strategy_migration.md @@ -271,7 +271,7 @@ New string argument `side` - which can be either `"long"` or `"short"`. ``` python hl_lines="3" class AwesomeStrategy(IStrategy): - def custom_entry_price(self, pair: str, current_time: datetime, proposed_rate: float, + def custom_entry_price(self, pair: str, trade: 'Trade', current_time: datetime, proposed_rate: float, entry_tag: Optional[str], **kwargs) -> float: return proposed_rate ``` @@ -280,7 +280,7 @@ After: ``` python hl_lines="3" class AwesomeStrategy(IStrategy): - def custom_entry_price(self, pair: str, current_time: datetime, proposed_rate: float, + def custom_entry_price(self, pair: str, trade: 'Trade', current_time: datetime, proposed_rate: float, entry_tag: Optional[str], side: str, **kwargs) -> float: return proposed_rate ``` diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index c895aa5c9..a4a938cac 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -937,7 +937,7 @@ class FreqtradeBot(LoggingMixin): # Don't call custom_entry_price in order-adjust scenario custom_entry_price = strategy_safe_wrapper(self.strategy.custom_entry_price, default_retval=enter_limit_requested)( - pair=pair, current_time=datetime.now(timezone.utc), + pair=pair, trade=trade, current_time=datetime.now(timezone.utc), proposed_rate=enter_limit_requested, entry_tag=entry_tag, side=trade_side, ) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 4b267b315..8f7ec4ab2 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -738,7 +738,7 @@ class Backtesting: if order_type == 'limit': new_rate = strategy_safe_wrapper(self.strategy.custom_entry_price, default_retval=propose_rate)( - pair=pair, current_time=current_time, + pair=pair, trade=trade, current_time=current_time, proposed_rate=propose_rate, entry_tag=entry_tag, side=direction, ) # default value is the open rate diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 94c78f3c7..2c3dd5790 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -395,7 +395,8 @@ class IStrategy(ABC, HyperStrategyMixin): """ return self.stoploss - def custom_entry_price(self, pair: str, current_time: datetime, proposed_rate: float, + def custom_entry_price(self, pair: str, trade: Trade, current_time: datetime, + proposed_rate: float, entry_tag: Optional[str], side: str, **kwargs) -> float: """ Custom entry price logic, returning the new entry price. diff --git a/tests/strategy/strats/strategy_test_v3_custom_entry_price.py b/tests/strategy/strats/strategy_test_v3_custom_entry_price.py index 872984156..b24782f82 100644 --- a/tests/strategy/strats/strategy_test_v3_custom_entry_price.py +++ b/tests/strategy/strats/strategy_test_v3_custom_entry_price.py @@ -6,6 +6,8 @@ from typing import Optional from pandas import DataFrame from strategy_test_v3 import StrategyTestV3 +from freqtrade.persistence import Trade + class StrategyTestV3CustomEntryPrice(StrategyTestV3): """ @@ -31,7 +33,7 @@ class StrategyTestV3CustomEntryPrice(StrategyTestV3): def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: return dataframe - def custom_entry_price(self, pair: str, current_time: datetime, proposed_rate: float, + def custom_entry_price(self, pair: str, trade: 'Trade', current_time: datetime, proposed_rate: float, entry_tag: Optional[str], side: str, **kwargs) -> float: return self.new_entry_price From 91c710408a15c995716429b2d1937d0d365b8ccf Mon Sep 17 00:00:00 2001 From: Axel-CH Date: Sat, 16 Sep 2023 03:36:36 -0400 Subject: [PATCH 02/12] fix flake8, set trade object param as Optional --- freqtrade/strategy/interface.py | 2 +- tests/strategy/strats/strategy_test_v3_custom_entry_price.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 2c3dd5790..8830293e9 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -395,7 +395,7 @@ class IStrategy(ABC, HyperStrategyMixin): """ return self.stoploss - def custom_entry_price(self, pair: str, trade: Trade, current_time: datetime, + def custom_entry_price(self, pair: str, trade: Optional[Trade], current_time: datetime, proposed_rate: float, entry_tag: Optional[str], side: str, **kwargs) -> float: """ diff --git a/tests/strategy/strats/strategy_test_v3_custom_entry_price.py b/tests/strategy/strats/strategy_test_v3_custom_entry_price.py index b24782f82..04e044977 100644 --- a/tests/strategy/strats/strategy_test_v3_custom_entry_price.py +++ b/tests/strategy/strats/strategy_test_v3_custom_entry_price.py @@ -33,7 +33,8 @@ class StrategyTestV3CustomEntryPrice(StrategyTestV3): def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: return dataframe - def custom_entry_price(self, pair: str, trade: 'Trade', current_time: datetime, proposed_rate: float, + def custom_entry_price(self, pair: str, trade: Trade, current_time: datetime, + proposed_rate: float, entry_tag: Optional[str], side: str, **kwargs) -> float: return self.new_entry_price From 1d0f1bd1ee37f00a8750b6ff5ca4a36c6f32bb70 Mon Sep 17 00:00:00 2001 From: Axel-CH Date: Sat, 16 Sep 2023 13:13:23 -0400 Subject: [PATCH 03/12] update doc --- docs/strategy-callbacks.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index a818315ce..321874f6f 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -510,6 +510,8 @@ Each of these methods are called right before placing an order on the exchange. !!! Note If your custom pricing function return None or an invalid value, price will fall back to `proposed_rate`, which is based on the regular pricing configuration. +!!! Note + Within this function you will have access to the Trade object, giving you the flexibility to adjust your current entry or exit price in relation to the current trade status. This object is available as soon as the first entry order associated with the trade is created. ### Custom order entry and exit price example ``` python From 3f4715ba496599fd1dc603135ff795639fb46c12 Mon Sep 17 00:00:00 2001 From: Axel-CH Date: Sat, 16 Sep 2023 13:20:10 -0400 Subject: [PATCH 04/12] update custom_entry_price docstring --- freqtrade/strategy/interface.py | 1 + 1 file changed, 1 insertion(+) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 8830293e9..82e9f5609 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -406,6 +406,7 @@ class IStrategy(ABC, HyperStrategyMixin): When not implemented by a strategy, returns None, orderbook is used to set entry price :param pair: Pair that's currently analyzed + :param trade: trade object. :param current_time: datetime object, containing the current datetime :param proposed_rate: Rate, calculated based on pricing settings in exit_pricing. :param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal. From 5b857aeaf048daa343149b8ecf56d51b2cd0bbe2 Mon Sep 17 00:00:00 2001 From: Axel-CH Date: Sat, 16 Sep 2023 13:46:56 -0400 Subject: [PATCH 05/12] fix custom_entry_price trade object type test --- freqtrade/strategy/interface.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 82e9f5609..a1101875f 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -16,7 +16,7 @@ from freqtrade.enums import (CandleType, ExitCheckTuple, ExitType, MarketDirecti from freqtrade.exceptions import OperationalException, StrategyError from freqtrade.exchange import timeframe_to_minutes, timeframe_to_next_date, timeframe_to_seconds from freqtrade.misc import remove_entry_exit_signals -from freqtrade.persistence import Order, PairLocks, Trade +from freqtrade.persistence import LocalTrade, Order, PairLocks, Trade from freqtrade.strategy.hyper import HyperStrategyMixin from freqtrade.strategy.informative_decorator import (InformativeData, PopulateIndicators, _create_and_merge_informative_pair, @@ -395,8 +395,8 @@ class IStrategy(ABC, HyperStrategyMixin): """ return self.stoploss - def custom_entry_price(self, pair: str, trade: Optional[Trade], current_time: datetime, - proposed_rate: float, + def custom_entry_price(self, pair: str, trade: Union[Trade, LocalTrade, None], + current_time: datetime, proposed_rate: float, entry_tag: Optional[str], side: str, **kwargs) -> float: """ Custom entry price logic, returning the new entry price. From a7cd9d77f202f3f535d465d98f315ea9ec8a1e70 Mon Sep 17 00:00:00 2001 From: Axel-CH Date: Sat, 16 Sep 2023 13:58:59 -0400 Subject: [PATCH 06/12] ignore custom_entry_price trade object type test, remove LocalTrade as type --- freqtrade/freqtradebot.py | 4 +++- freqtrade/optimize/backtesting.py | 4 +++- freqtrade/strategy/interface.py | 4 ++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index a4a938cac..0d145db3b 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -937,7 +937,9 @@ class FreqtradeBot(LoggingMixin): # Don't call custom_entry_price in order-adjust scenario custom_entry_price = strategy_safe_wrapper(self.strategy.custom_entry_price, default_retval=enter_limit_requested)( - pair=pair, trade=trade, current_time=datetime.now(timezone.utc), + pair=pair, + trade=trade, # type: ignore[arg-type] + current_time=datetime.now(timezone.utc), proposed_rate=enter_limit_requested, entry_tag=entry_tag, side=trade_side, ) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 8f7ec4ab2..6db996589 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -738,7 +738,9 @@ class Backtesting: if order_type == 'limit': new_rate = strategy_safe_wrapper(self.strategy.custom_entry_price, default_retval=propose_rate)( - pair=pair, trade=trade, current_time=current_time, + pair=pair, + trade=trade, # type: ignore[arg-type] + current_time=current_time, proposed_rate=propose_rate, entry_tag=entry_tag, side=direction, ) # default value is the open rate diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index a1101875f..0e0a29a45 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -16,7 +16,7 @@ from freqtrade.enums import (CandleType, ExitCheckTuple, ExitType, MarketDirecti from freqtrade.exceptions import OperationalException, StrategyError from freqtrade.exchange import timeframe_to_minutes, timeframe_to_next_date, timeframe_to_seconds from freqtrade.misc import remove_entry_exit_signals -from freqtrade.persistence import LocalTrade, Order, PairLocks, Trade +from freqtrade.persistence import Order, PairLocks, Trade from freqtrade.strategy.hyper import HyperStrategyMixin from freqtrade.strategy.informative_decorator import (InformativeData, PopulateIndicators, _create_and_merge_informative_pair, @@ -395,7 +395,7 @@ class IStrategy(ABC, HyperStrategyMixin): """ return self.stoploss - def custom_entry_price(self, pair: str, trade: Union[Trade, LocalTrade, None], + def custom_entry_price(self, pair: str, trade: Trade, current_time: datetime, proposed_rate: float, entry_tag: Optional[str], side: str, **kwargs) -> float: """ From d81fdeb3ed4c1f6c4d7ae5873b1b2411db31421b Mon Sep 17 00:00:00 2001 From: Axel CHERUBIN Date: Sun, 17 Sep 2023 03:06:00 -0400 Subject: [PATCH 07/12] Update docs/strategy-callbacks.md Co-authored-by: Matthias --- docs/strategy-callbacks.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index 321874f6f..8e7bee5f9 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -522,7 +522,7 @@ class AwesomeStrategy(IStrategy): # ... populate_* methods - def custom_entry_price(self, pair: str, trade: 'Trade', current_time: datetime, proposed_rate: float, + def custom_entry_price(self, pair: str, trade: Optional['Trade'], current_time: datetime, proposed_rate: float, entry_tag: Optional[str], side: str, **kwargs) -> float: dataframe, last_updated = self.dp.get_analyzed_dataframe(pair=pair, From 224213840d90994acfae181ee96dfcb9baec3683 Mon Sep 17 00:00:00 2001 From: Axel-CH Date: Sun, 17 Sep 2023 03:13:40 -0400 Subject: [PATCH 08/12] update trade object as optional parameter --- docs/strategy_migration.md | 4 ++-- freqtrade/freqtradebot.py | 2 +- freqtrade/strategy/interface.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/strategy_migration.md b/docs/strategy_migration.md index aaa655473..9e6f56e49 100644 --- a/docs/strategy_migration.md +++ b/docs/strategy_migration.md @@ -271,7 +271,7 @@ New string argument `side` - which can be either `"long"` or `"short"`. ``` python hl_lines="3" class AwesomeStrategy(IStrategy): - def custom_entry_price(self, pair: str, trade: 'Trade', current_time: datetime, proposed_rate: float, + def custom_entry_price(self, pair: str, current_time: datetime, proposed_rate: float, entry_tag: Optional[str], **kwargs) -> float: return proposed_rate ``` @@ -280,7 +280,7 @@ After: ``` python hl_lines="3" class AwesomeStrategy(IStrategy): - def custom_entry_price(self, pair: str, trade: 'Trade', current_time: datetime, proposed_rate: float, + def custom_entry_price(self, pair: str, trade: Optional[Trade], current_time: datetime, proposed_rate: float, entry_tag: Optional[str], side: str, **kwargs) -> float: return proposed_rate ``` diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 0d145db3b..1dd7884e0 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -938,7 +938,7 @@ class FreqtradeBot(LoggingMixin): custom_entry_price = strategy_safe_wrapper(self.strategy.custom_entry_price, default_retval=enter_limit_requested)( pair=pair, - trade=trade, # type: ignore[arg-type] + trade=trade, current_time=datetime.now(timezone.utc), proposed_rate=enter_limit_requested, entry_tag=entry_tag, side=trade_side, diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 0e0a29a45..1c9a18b5c 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -395,7 +395,7 @@ class IStrategy(ABC, HyperStrategyMixin): """ return self.stoploss - def custom_entry_price(self, pair: str, trade: Trade, + def custom_entry_price(self, pair: str, trade: Optional[Trade], current_time: datetime, proposed_rate: float, entry_tag: Optional[str], side: str, **kwargs) -> float: """ From 2bc0c4ecd5f8f46080fcf54843184d84221b533e Mon Sep 17 00:00:00 2001 From: Axel-CH Date: Sun, 17 Sep 2023 03:17:07 -0400 Subject: [PATCH 09/12] update docstring --- freqtrade/strategy/interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 1c9a18b5c..5cdbb6bf6 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -406,7 +406,7 @@ class IStrategy(ABC, HyperStrategyMixin): When not implemented by a strategy, returns None, orderbook is used to set entry price :param pair: Pair that's currently analyzed - :param trade: trade object. + :param trade: trade object (None for initial entries). :param current_time: datetime object, containing the current datetime :param proposed_rate: Rate, calculated based on pricing settings in exit_pricing. :param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal. From 29a5e049b92ff7812ba329b60fef8cb9132413e8 Mon Sep 17 00:00:00 2001 From: Axel-CH Date: Sun, 17 Sep 2023 03:30:03 -0400 Subject: [PATCH 10/12] edit note wording for custom_entry_price --- docs/strategy-callbacks.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index 8e7bee5f9..d4950cfdd 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -511,7 +511,7 @@ Each of these methods are called right before placing an order on the exchange. If your custom pricing function return None or an invalid value, price will fall back to `proposed_rate`, which is based on the regular pricing configuration. !!! Note - Within this function you will have access to the Trade object, giving you the flexibility to adjust your current entry or exit price in relation to the current trade status. This object is available as soon as the first entry order associated with the trade is created. + Using custom_entry_price, the Trade object will be available as soon as the first entry order associated with the trade is created, for the first entry, `trade` parameter value will be `None`. ### Custom order entry and exit price example ``` python From 158bf097744796a1072dd72a362dbfb3f7b845b2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 17 Sep 2023 19:40:44 +0200 Subject: [PATCH 11/12] Fix type checking error --- tests/strategy/strats/strategy_test_v3_custom_entry_price.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/strategy/strats/strategy_test_v3_custom_entry_price.py b/tests/strategy/strats/strategy_test_v3_custom_entry_price.py index 04e044977..607ff6e1e 100644 --- a/tests/strategy/strats/strategy_test_v3_custom_entry_price.py +++ b/tests/strategy/strats/strategy_test_v3_custom_entry_price.py @@ -33,7 +33,7 @@ class StrategyTestV3CustomEntryPrice(StrategyTestV3): def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: return dataframe - def custom_entry_price(self, pair: str, trade: Trade, current_time: datetime, + def custom_entry_price(self, pair: str, trade: Optional[Trade], current_time: datetime, proposed_rate: float, entry_tag: Optional[str], side: str, **kwargs) -> float: From f6fce2162c4238bdf279b92dc214566c6129bf7d Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 17 Sep 2023 19:57:13 +0200 Subject: [PATCH 12/12] Add new parameter to strategy template --- docs/strategy-callbacks.md | 1 + freqtrade/freqtradebot.py | 3 +-- .../strategy_subtemplates/strategy_methods_advanced.j2 | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index d4950cfdd..e42aa39d2 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -512,6 +512,7 @@ Each of these methods are called right before placing an order on the exchange. !!! Note Using custom_entry_price, the Trade object will be available as soon as the first entry order associated with the trade is created, for the first entry, `trade` parameter value will be `None`. + ### Custom order entry and exit price example ``` python diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 1dd7884e0..983d5c826 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -937,8 +937,7 @@ class FreqtradeBot(LoggingMixin): # Don't call custom_entry_price in order-adjust scenario custom_entry_price = strategy_safe_wrapper(self.strategy.custom_entry_price, default_retval=enter_limit_requested)( - pair=pair, - trade=trade, + pair=pair, trade=trade, current_time=datetime.now(timezone.utc), proposed_rate=enter_limit_requested, entry_tag=entry_tag, side=trade_side, diff --git a/freqtrade/templates/strategy_subtemplates/strategy_methods_advanced.j2 b/freqtrade/templates/strategy_subtemplates/strategy_methods_advanced.j2 index 4e1875084..6fad129c7 100644 --- a/freqtrade/templates/strategy_subtemplates/strategy_methods_advanced.j2 +++ b/freqtrade/templates/strategy_subtemplates/strategy_methods_advanced.j2 @@ -13,7 +13,8 @@ def bot_loop_start(self, current_time: datetime, **kwargs) -> None: """ pass -def custom_entry_price(self, pair: str, current_time: 'datetime', proposed_rate: float, +def custom_entry_price(self, pair: str, trade: Optional['Trade'], + current_time: 'datetime', proposed_rate: float, entry_tag: 'Optional[str]', side: str, **kwargs) -> float: """ Custom entry price logic, returning the new entry price. @@ -23,6 +24,7 @@ def custom_entry_price(self, pair: str, current_time: 'datetime', proposed_rate: When not implemented by a strategy, returns None, orderbook is used to set entry price :param pair: Pair that's currently analyzed + :param trade: trade object (None for initial entries). :param current_time: datetime object, containing the current datetime :param proposed_rate: Rate, calculated based on pricing settings in exit_pricing. :param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal.