From 0b90e1d30921b8cec6e97e612e44e51ea225b0c8 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Fri, 1 Apr 2022 08:39:56 -0600 Subject: [PATCH 01/13] Added bot_start callback to strategy interface --- freqtrade/freqtradebot.py | 2 ++ freqtrade/strategy/interface.py | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 57d7cac3c..bee6c4746 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/strategy/interface.py b/freqtrade/strategy/interface.py index 300010b83..40744ed08 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). From bf7da35e31a3d9a98fff35cd098ecaf83f6d91f2 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Fri, 1 Apr 2022 09:25:03 -0600 Subject: [PATCH 02/13] strategy callback on_whitelist_update --- freqtrade/freqtradebot.py | 4 ++++ freqtrade/strategy/interface.py | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index bee6c4746..949d821b3 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -179,6 +179,7 @@ class FreqtradeBot(LoggingMixin): # Query trades from persistence layer trades = Trade.get_open_trades() + current_pair_whitelist = self.active_pair_whitelist self.active_pair_whitelist = self._refresh_active_whitelist(trades) # Refreshing candles @@ -214,6 +215,9 @@ class FreqtradeBot(LoggingMixin): Trade.commit() self.last_process = datetime.now(timezone.utc) + if current_pair_whitelist != self.active_pair_whitelist: + self.strategy.on_whitelist_update() + def process_stopped(self) -> None: """ Close all orders that were left open diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 40744ed08..00d18fa7b 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -209,6 +209,13 @@ class IStrategy(ABC, HyperStrategyMixin): """ pass + def on_whitelist_update(self, **kwargs) -> None: + """ + Called every time the whitelist updates + :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. + """ + pass + def check_buy_timeout(self, pair: str, trade: Trade, order: Order, current_time: datetime, **kwargs) -> bool: """ From d92761b2b168a19eea764864c5fb9612a379c510 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Sun, 3 Apr 2022 02:53:44 -0600 Subject: [PATCH 03/13] Revert "strategy callback on_whitelist_update" This reverts commit 39798dc1192161c3060830dd4684571aa86b7821. --- freqtrade/freqtradebot.py | 4 ---- freqtrade/strategy/interface.py | 7 ------- 2 files changed, 11 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 949d821b3..bee6c4746 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -179,7 +179,6 @@ class FreqtradeBot(LoggingMixin): # Query trades from persistence layer trades = Trade.get_open_trades() - current_pair_whitelist = self.active_pair_whitelist self.active_pair_whitelist = self._refresh_active_whitelist(trades) # Refreshing candles @@ -215,9 +214,6 @@ class FreqtradeBot(LoggingMixin): Trade.commit() self.last_process = datetime.now(timezone.utc) - if current_pair_whitelist != self.active_pair_whitelist: - self.strategy.on_whitelist_update() - def process_stopped(self) -> None: """ Close all orders that were left open diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 00d18fa7b..40744ed08 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -209,13 +209,6 @@ class IStrategy(ABC, HyperStrategyMixin): """ pass - def on_whitelist_update(self, **kwargs) -> None: - """ - Called every time the whitelist updates - :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. - """ - pass - def check_buy_timeout(self, pair: str, trade: Trade, order: Order, current_time: datetime, **kwargs) -> bool: """ From 4fd904e0a91ecf03020060c3d718382775bd143b Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Sun, 3 Apr 2022 10:28:09 -0600 Subject: [PATCH 04/13] added bot_start to backtesting --- freqtrade/optimize/backtesting.py | 1 + 1 file changed, 1 insertion(+) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 27e14ba93..1789290bc 100755 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -182,6 +182,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): From e09b4498fa419f0088b38cec2cef6c78307ee2e6 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Mon, 25 Apr 2022 17:30:49 -0600 Subject: [PATCH 05/13] added bot_start call to plot/plotting --- freqtrade/plot/plotting.py | 1 + 1 file changed, 1 insertion(+) 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'] From e76c6e8ad31a525802ec35fa0dd8c6e264d32328 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Mon, 25 Apr 2022 17:32:48 -0600 Subject: [PATCH 06/13] added bot_start call to edge_positioning.__init__ --- freqtrade/edge/edge_positioning.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/freqtrade/edge/edge_positioning.py b/freqtrade/edge/edge_positioning.py index 2fe41a17b..5dc84f172 100644 --- a/freqtrade/edge/edge_positioning.py +++ b/freqtrade/edge/edge_positioning.py @@ -90,6 +90,8 @@ class Edge: self.config['exchange']['pair_whitelist'], list(self.exchange.markets))[0]) except IndexError: self.fee = None + + strategy.bot_start() def calculate(self, pairs: List[str]) -> bool: if self.fee is None and pairs: From 810e190e164be61a530e4d550c246dddbcdb4f1c Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Mon, 25 Apr 2022 17:46:40 -0600 Subject: [PATCH 07/13] added tests for bot_start --- tests/optimize/test_backtesting.py | 1 + tests/optimize/test_edge_cli.py | 1 + tests/strategy/strats/strategy_test_v3.py | 5 +++++ tests/strategy/test_default_strategy.py | 1 + tests/test_plotting.py | 1 + 5 files changed, 9 insertions(+) diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index d7ee4a042..16d1aa2a5 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..d9711b318 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_conf['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 [] diff --git a/tests/strategy/test_default_strategy.py b/tests/strategy/test_default_strategy.py index 5cb8fce16..a60274afd 100644 --- a/tests/strategy/test_default_strategy.py +++ b/tests/strategy/test_default_strategy.py @@ -32,6 +32,7 @@ def test_strategy_test_v3(result, fee, is_short, side): assert type(indicators) is DataFrame assert type(strategy.populate_buy_trend(indicators, metadata)) is DataFrame assert type(strategy.populate_sell_trend(indicators, metadata)) is DataFrame + assert strategy.bot_started is True trade = Trade( open_rate=19_000, diff --git a/tests/test_plotting.py b/tests/test_plotting.py index 97f367608..f0a28c4eb 100644 --- a/tests/test_plotting.py +++ b/tests/test_plotting.py @@ -58,6 +58,7 @@ def test_init_plotscript(default_conf, mocker, testdatadir): assert "ohlcv" in ret assert "TRX/BTC" in ret["ohlcv"] assert "ADA/BTC" in ret["ohlcv"] + assert default_conf['strategy'].bot_started is True def test_add_indicators(default_conf, testdatadir, caplog): From 7f035a9d5384993dc195a0a5d8f2e22c5fc74d75 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Mon, 25 Apr 2022 17:59:50 -0600 Subject: [PATCH 08/13] added docs for bot_start --- docs/strategy-callbacks.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index 7ec600a58..a53a6992c 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,31 @@ 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 bot starts. +This can be used to perform actions that must only be performed once and runs after dataprovider and wallet are set + +``` python +import asyncio + +class AwesomeStrategy(IStrategy): + + # ... populate_* methods + + async def some_asynchronous_task(self): + self.dp['remote_data'] = requests.get('https://some_remote_source.example.com') + + 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. + """ + self.loop = asyncio.new_event_loop() + asyncio.set_event_loop(self.loop) + self.loop.run_until_complete(self.some_asynchronous_task()) + +``` ## 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). From a35dc843ea75fe8a4c5452ab8534b0c077bfc106 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Mon, 25 Apr 2022 22:39:58 -0600 Subject: [PATCH 09/13] removed asyncio from bot_start example --- docs/strategy-callbacks.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index a53a6992c..e0cfbe8c1 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -34,17 +34,12 @@ class AwesomeStrategy(IStrategy): # ... populate_* methods - async def some_asynchronous_task(self): - self.dp['remote_data'] = requests.get('https://some_remote_source.example.com') - 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. """ - self.loop = asyncio.new_event_loop() - asyncio.set_event_loop(self.loop) - self.loop.run_until_complete(self.some_asynchronous_task()) + self.dp['remote_data'] = requests.get('https://some_remote_source.example.com') ``` ## Bot loop start From 788d9f5b55cef5657ad476d6feef8cbd227a3dec Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Fri, 29 Apr 2022 22:20:26 -0600 Subject: [PATCH 10/13] updated bot_start documentation with working example --- docs/strategy-callbacks.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index e0cfbe8c1..b01959e10 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -39,7 +39,10 @@ class AwesomeStrategy(IStrategy): Called only once after bot instantiation. :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. """ - self.dp['remote_data'] = requests.get('https://some_remote_source.example.com') + 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.remote_data = requests.get('https://some_remote_source.example.com') ``` ## Bot loop start From 23431a71064367496d8250ca7a7dd20b97da63d4 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Fri, 29 Apr 2022 22:21:22 -0600 Subject: [PATCH 11/13] removed invalid plotting and test_default_strategy tests for bot_start, edited edge test --- tests/optimize/test_edge_cli.py | 2 +- tests/strategy/test_default_strategy.py | 1 - tests/test_plotting.py | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/optimize/test_edge_cli.py b/tests/optimize/test_edge_cli.py index d9711b318..8241a5362 100644 --- a/tests/optimize/test_edge_cli.py +++ b/tests/optimize/test_edge_cli.py @@ -94,7 +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_conf['strategy'].bot_started is True + assert edge_cli.strategy.bot_started is True def test_edge_init_fee(mocker, edge_conf) -> None: diff --git a/tests/strategy/test_default_strategy.py b/tests/strategy/test_default_strategy.py index a60274afd..5cb8fce16 100644 --- a/tests/strategy/test_default_strategy.py +++ b/tests/strategy/test_default_strategy.py @@ -32,7 +32,6 @@ def test_strategy_test_v3(result, fee, is_short, side): assert type(indicators) is DataFrame assert type(strategy.populate_buy_trend(indicators, metadata)) is DataFrame assert type(strategy.populate_sell_trend(indicators, metadata)) is DataFrame - assert strategy.bot_started is True trade = Trade( open_rate=19_000, diff --git a/tests/test_plotting.py b/tests/test_plotting.py index f0a28c4eb..97f367608 100644 --- a/tests/test_plotting.py +++ b/tests/test_plotting.py @@ -58,7 +58,6 @@ def test_init_plotscript(default_conf, mocker, testdatadir): assert "ohlcv" in ret assert "TRX/BTC" in ret["ohlcv"] assert "ADA/BTC" in ret["ohlcv"] - assert default_conf['strategy'].bot_started is True def test_add_indicators(default_conf, testdatadir, caplog): From 8756e7d9a1febecfae12649cc8ed21952d2f0086 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Fri, 29 Apr 2022 23:35:08 -0600 Subject: [PATCH 12/13] flake8 linting --- freqtrade/edge/edge_positioning.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/edge/edge_positioning.py b/freqtrade/edge/edge_positioning.py index 5dc84f172..fb9041574 100644 --- a/freqtrade/edge/edge_positioning.py +++ b/freqtrade/edge/edge_positioning.py @@ -90,7 +90,7 @@ class Edge: self.config['exchange']['pair_whitelist'], list(self.exchange.markets))[0]) except IndexError: self.fee = None - + strategy.bot_start() def calculate(self, pairs: List[str]) -> bool: From 09b74cebce95c80bec0f6982bf9b865523e49874 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 30 Apr 2022 08:55:07 +0200 Subject: [PATCH 13/13] Move edge bot_loop_start to edge_cli (otherwise it's called twice when running trade mode with edge on). --- docs/strategy-callbacks.md | 6 +++--- freqtrade/edge/edge_positioning.py | 2 -- freqtrade/optimize/edge_cli.py | 1 + 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index 27777c2ce..5ff499b01 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -24,11 +24,11 @@ Currently available callbacks: ## Bot start -A simple callback which is called once when the bot starts. +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 asyncio +import requests class AwesomeStrategy(IStrategy): @@ -42,7 +42,7 @@ class AwesomeStrategy(IStrategy): 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.remote_data = requests.get('https://some_remote_source.example.com') + self.cust_remote_data = requests.get('https://some_remote_source.example.com') ``` ## Bot loop start diff --git a/freqtrade/edge/edge_positioning.py b/freqtrade/edge/edge_positioning.py index fb9041574..2fe41a17b 100644 --- a/freqtrade/edge/edge_positioning.py +++ b/freqtrade/edge/edge_positioning.py @@ -91,8 +91,6 @@ class Edge: except IndexError: self.fee = None - strategy.bot_start() - def calculate(self, pairs: List[str]) -> bool: if self.fee is None and pairs: self.fee = self.exchange.get_fee(pairs[0]) 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'])