diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index c5639d581..5ff499b01 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -7,6 +7,7 @@ Depending on the callback used, they may be called when entering / exiting a tra Currently available callbacks: +* [`bot_start()`](#bot-start) * [`bot_loop_start()`](#bot-loop-start) * [`custom_stake_amount()`](#stake-size-management) * [`custom_exit()`](#custom-exit-signal) @@ -21,6 +22,29 @@ Currently available callbacks: !!! Tip "Callback calling sequence" You can find the callback calling sequence in [bot-basics](bot-basics.md#bot-execution-logic) +## Bot start + +A simple callback which is called once when the strategy is loaded. +This can be used to perform actions that must only be performed once and runs after dataprovider and wallet are set + +``` python +import requests + +class AwesomeStrategy(IStrategy): + + # ... populate_* methods + + def bot_start(self, **kwargs) -> None: + """ + Called only once after bot instantiation. + :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. + """ + if self.config['runmode'].value in ('live', 'dry_run'): + # Assign this to the class by using self.* + # can then be used by populate_* methods + self.cust_remote_data = requests.get('https://some_remote_source.example.com') + +``` ## Bot loop start A simple callback which is called once at the start of every bot throttling iteration (roughly every 5 seconds, unless configured differently). diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 7c20a7f60..b6f0daeed 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -122,6 +122,8 @@ class FreqtradeBot(LoggingMixin): self._schedule.every().day.at(t).do(update) self.last_process = datetime(1970, 1, 1, tzinfo=timezone.utc) + self.strategy.bot_start() + def notify_status(self, msg: str) -> None: """ Public method for users of this class (worker, etc.) to send notifications diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index db4496f0f..7456c40cf 100755 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -187,6 +187,7 @@ class Backtesting: # since a "perfect" stoploss-exit is assumed anyway # And the regular "stoploss" function would not apply to that case self.strategy.order_types['stoploss_on_exchange'] = False + self.strategy.bot_start() def _load_protections(self, strategy: IStrategy): if self.config.get('enable_protections', False): diff --git a/freqtrade/optimize/edge_cli.py b/freqtrade/optimize/edge_cli.py index cc9bafb0b..30eabecd0 100644 --- a/freqtrade/optimize/edge_cli.py +++ b/freqtrade/optimize/edge_cli.py @@ -44,6 +44,7 @@ class EdgeCli: self.edge._timerange = TimeRange.parse_timerange(None if self.config.get( 'timerange') is None else str(self.config.get('timerange'))) + self.strategy.bot_start() def start(self) -> None: result = self.edge.calculate(self.config['exchange']['pair_whitelist']) diff --git a/freqtrade/plot/plotting.py b/freqtrade/plot/plotting.py index 747248be7..5337016f3 100644 --- a/freqtrade/plot/plotting.py +++ b/freqtrade/plot/plotting.py @@ -610,6 +610,7 @@ def load_and_plot_trades(config: Dict[str, Any]): exchange = ExchangeResolver.load_exchange(config['exchange']['name'], config) IStrategy.dp = DataProvider(config, exchange) + strategy.bot_start() plot_elements = init_plotscript(config, list(exchange.markets), strategy.startup_candle_count) timerange = plot_elements['timerange'] trades = plot_elements['trades'] diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 4f9e91b56..a23269770 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -193,6 +193,13 @@ class IStrategy(ABC, HyperStrategyMixin): """ return self.populate_sell_trend(dataframe, metadata) + def bot_start(self, **kwargs) -> None: + """ + Called only once after bot instantiation. + :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. + """ + pass + def bot_loop_start(self, **kwargs) -> None: """ Called at the start of the bot iteration (one loop). diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index 7494155b4..c87a0ef73 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -312,6 +312,7 @@ def test_backtesting_init(mocker, default_conf, order_types) -> None: get_fee.assert_called() assert backtesting.fee == 0.5 assert not backtesting.strategy.order_types["stoploss_on_exchange"] + assert backtesting.strategy.bot_started is True def test_backtesting_init_no_timeframe(mocker, default_conf, caplog) -> None: diff --git a/tests/optimize/test_edge_cli.py b/tests/optimize/test_edge_cli.py index f0f436a43..8241a5362 100644 --- a/tests/optimize/test_edge_cli.py +++ b/tests/optimize/test_edge_cli.py @@ -94,6 +94,7 @@ def test_edge_init(mocker, edge_conf) -> None: assert edge_cli.config == edge_conf assert edge_cli.config['stake_amount'] == 'unlimited' assert callable(edge_cli.edge.calculate) + assert edge_cli.strategy.bot_started is True def test_edge_init_fee(mocker, edge_conf) -> None: diff --git a/tests/strategy/strats/strategy_test_v3.py b/tests/strategy/strats/strategy_test_v3.py index 372e29412..df83d3663 100644 --- a/tests/strategy/strats/strategy_test_v3.py +++ b/tests/strategy/strats/strategy_test_v3.py @@ -82,6 +82,11 @@ class StrategyTestV3(IStrategy): # }) # return prot + bot_started = False + + def bot_start(self): + self.bot_started = True + def informative_pairs(self): return []