Merge pull request #8272 from paranoidandy/bot-loop-start-every-candle-bt
Make strategy.bot_loop_start run once per candle in backtest
This commit is contained in:
commit
31a396bc25
@ -60,10 +60,10 @@ This loop will be repeated again and again until the bot is stopped.
|
|||||||
|
|
||||||
* Load historic data for configured pairlist.
|
* Load historic data for configured pairlist.
|
||||||
* Calls `bot_start()` once.
|
* Calls `bot_start()` once.
|
||||||
* Calls `bot_loop_start()` once.
|
|
||||||
* Calculate indicators (calls `populate_indicators()` once per pair).
|
* Calculate indicators (calls `populate_indicators()` once per pair).
|
||||||
* Calculate entry / exit signals (calls `populate_entry_trend()` and `populate_exit_trend()` once per pair).
|
* Calculate entry / exit signals (calls `populate_entry_trend()` and `populate_exit_trend()` once per pair).
|
||||||
* Loops per candle simulating entry and exit points.
|
* Loops per candle simulating entry and exit points.
|
||||||
|
* Calls `bot_loop_start()` strategy callback.
|
||||||
* Check for Order timeouts, either via the `unfilledtimeout` configuration, or via `check_entry_timeout()` / `check_exit_timeout()` strategy callbacks.
|
* Check for Order timeouts, either via the `unfilledtimeout` configuration, or via `check_entry_timeout()` / `check_exit_timeout()` strategy callbacks.
|
||||||
* Calls `adjust_entry_price()` strategy callback for open entry orders.
|
* Calls `adjust_entry_price()` strategy callback for open entry orders.
|
||||||
* Check for trade entry signals (`enter_long` / `enter_short` columns).
|
* Check for trade entry signals (`enter_long` / `enter_short` columns).
|
||||||
|
@ -51,7 +51,8 @@ During hyperopt, this runs only once at startup.
|
|||||||
|
|
||||||
## Bot loop start
|
## 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).
|
A simple callback which is called once at the start of every bot throttling iteration in dry/live mode (roughly every 5
|
||||||
|
seconds, unless configured differently) or once per candle in backtest/hyperopt mode.
|
||||||
This can be used to perform calculations which are pair independent (apply to all pairs), loading of external data, etc.
|
This can be used to perform calculations which are pair independent (apply to all pairs), loading of external data, etc.
|
||||||
|
|
||||||
``` python
|
``` python
|
||||||
@ -61,11 +62,12 @@ class AwesomeStrategy(IStrategy):
|
|||||||
|
|
||||||
# ... populate_* methods
|
# ... populate_* methods
|
||||||
|
|
||||||
def bot_loop_start(self, **kwargs) -> None:
|
def bot_loop_start(self, current_time: datetime, **kwargs) -> None:
|
||||||
"""
|
"""
|
||||||
Called at the start of the bot iteration (one loop).
|
Called at the start of the bot iteration (one loop).
|
||||||
Might be used to perform pair-independent tasks
|
Might be used to perform pair-independent tasks
|
||||||
(e.g. gather some remote resource for comparison)
|
(e.g. gather some remote resource for comparison)
|
||||||
|
:param current_time: datetime object, containing the current datetime
|
||||||
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
|
: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'):
|
if self.config['runmode'].value in ('live', 'dry_run'):
|
||||||
|
@ -212,7 +212,8 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
self.dataprovider.refresh(self.pairlists.create_pair_list(self.active_pair_whitelist),
|
self.dataprovider.refresh(self.pairlists.create_pair_list(self.active_pair_whitelist),
|
||||||
self.strategy.gather_informative_pairs())
|
self.strategy.gather_informative_pairs())
|
||||||
|
|
||||||
strategy_safe_wrapper(self.strategy.bot_loop_start, supress_error=True)()
|
strategy_safe_wrapper(self.strategy.bot_loop_start, supress_error=True)(
|
||||||
|
current_time=datetime.now(timezone.utc))
|
||||||
|
|
||||||
self.strategy.analyze(self.active_pair_whitelist)
|
self.strategy.analyze(self.active_pair_whitelist)
|
||||||
|
|
||||||
|
@ -205,7 +205,6 @@ class Backtesting:
|
|||||||
self.strategy.order_types['stoploss_on_exchange'] = False
|
self.strategy.order_types['stoploss_on_exchange'] = False
|
||||||
|
|
||||||
self.strategy.ft_bot_start()
|
self.strategy.ft_bot_start()
|
||||||
strategy_safe_wrapper(self.strategy.bot_loop_start, supress_error=True)()
|
|
||||||
|
|
||||||
def _load_protections(self, strategy: IStrategy):
|
def _load_protections(self, strategy: IStrategy):
|
||||||
if self.config.get('enable_protections', False):
|
if self.config.get('enable_protections', False):
|
||||||
@ -1155,6 +1154,8 @@ class Backtesting:
|
|||||||
while current_time <= end_date:
|
while current_time <= end_date:
|
||||||
open_trade_count_start = LocalTrade.bt_open_open_trade_count
|
open_trade_count_start = LocalTrade.bt_open_open_trade_count
|
||||||
self.check_abort()
|
self.check_abort()
|
||||||
|
strategy_safe_wrapper(self.strategy.bot_loop_start, supress_error=True)(
|
||||||
|
current_time=current_time)
|
||||||
for i, pair in enumerate(data):
|
for i, pair in enumerate(data):
|
||||||
row_index = indexes[pair]
|
row_index = indexes[pair]
|
||||||
row = self.validate_row(data, pair, row_index, current_time)
|
row = self.validate_row(data, pair, row_index, current_time)
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import logging
|
import logging
|
||||||
|
from datetime import datetime, timezone
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, List, Optional
|
from typing import Dict, List, Optional
|
||||||
|
|
||||||
@ -635,7 +636,7 @@ def load_and_plot_trades(config: Config):
|
|||||||
exchange = ExchangeResolver.load_exchange(config['exchange']['name'], config)
|
exchange = ExchangeResolver.load_exchange(config['exchange']['name'], config)
|
||||||
IStrategy.dp = DataProvider(config, exchange)
|
IStrategy.dp = DataProvider(config, exchange)
|
||||||
strategy.ft_bot_start()
|
strategy.ft_bot_start()
|
||||||
strategy.bot_loop_start()
|
strategy.bot_loop_start(datetime.now(timezone.utc))
|
||||||
plot_elements = init_plotscript(config, list(exchange.markets), strategy.startup_candle_count)
|
plot_elements = init_plotscript(config, list(exchange.markets), strategy.startup_candle_count)
|
||||||
timerange = plot_elements['timerange']
|
timerange = plot_elements['timerange']
|
||||||
trades = plot_elements['trades']
|
trades = plot_elements['trades']
|
||||||
|
@ -251,11 +251,12 @@ class IStrategy(ABC, HyperStrategyMixin):
|
|||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def bot_loop_start(self, **kwargs) -> None:
|
def bot_loop_start(self, current_time: datetime, **kwargs) -> None:
|
||||||
"""
|
"""
|
||||||
Called at the start of the bot iteration (one loop).
|
Called at the start of the bot iteration (one loop).
|
||||||
Might be used to perform pair-independent tasks
|
Might be used to perform pair-independent tasks
|
||||||
(e.g. gather some remote resource for comparison)
|
(e.g. gather some remote resource for comparison)
|
||||||
|
:param current_time: datetime object, containing the current datetime
|
||||||
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
|
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
def bot_loop_start(self, **kwargs) -> None:
|
def bot_loop_start(self, current_time: datetime, **kwargs) -> None:
|
||||||
"""
|
"""
|
||||||
Called at the start of the bot iteration (one loop).
|
Called at the start of the bot iteration (one loop).
|
||||||
Might be used to perform pair-independent tasks
|
Might be used to perform pair-independent tasks
|
||||||
@ -8,6 +8,7 @@ def bot_loop_start(self, **kwargs) -> None:
|
|||||||
For full documentation please go to https://www.freqtrade.io/en/latest/strategy-advanced/
|
For full documentation please go to https://www.freqtrade.io/en/latest/strategy-advanced/
|
||||||
|
|
||||||
When not implemented by a strategy, this simply does nothing.
|
When not implemented by a strategy, this simply does nothing.
|
||||||
|
:param current_time: datetime object, containing the current datetime
|
||||||
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
|
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
@ -344,7 +344,7 @@ def test_backtest_abort(default_conf, mocker, testdatadir) -> None:
|
|||||||
assert backtesting.progress.progress == 0
|
assert backtesting.progress.progress == 0
|
||||||
|
|
||||||
|
|
||||||
def test_backtesting_start(default_conf, mocker, testdatadir, caplog) -> None:
|
def test_backtesting_start(default_conf, mocker, caplog) -> None:
|
||||||
def get_timerange(input1):
|
def get_timerange(input1):
|
||||||
return Arrow(2017, 11, 14, 21, 17), Arrow(2017, 11, 14, 22, 59)
|
return Arrow(2017, 11, 14, 21, 17), Arrow(2017, 11, 14, 22, 59)
|
||||||
|
|
||||||
@ -367,6 +367,7 @@ def test_backtesting_start(default_conf, mocker, testdatadir, caplog) -> None:
|
|||||||
backtesting = Backtesting(default_conf)
|
backtesting = Backtesting(default_conf)
|
||||||
backtesting._set_strategy(backtesting.strategylist[0])
|
backtesting._set_strategy(backtesting.strategylist[0])
|
||||||
backtesting.strategy.bot_loop_start = MagicMock()
|
backtesting.strategy.bot_loop_start = MagicMock()
|
||||||
|
backtesting.strategy.bot_start = MagicMock()
|
||||||
backtesting.start()
|
backtesting.start()
|
||||||
# check the logs, that will contain the backtest result
|
# check the logs, that will contain the backtest result
|
||||||
exists = [
|
exists = [
|
||||||
@ -376,7 +377,8 @@ def test_backtesting_start(default_conf, mocker, testdatadir, caplog) -> None:
|
|||||||
for line in exists:
|
for line in exists:
|
||||||
assert log_has(line, caplog)
|
assert log_has(line, caplog)
|
||||||
assert backtesting.strategy.dp._pairlists is not None
|
assert backtesting.strategy.dp._pairlists is not None
|
||||||
assert backtesting.strategy.bot_loop_start.call_count == 1
|
assert backtesting.strategy.bot_start.call_count == 1
|
||||||
|
assert backtesting.strategy.bot_loop_start.call_count == 0
|
||||||
assert sbs.call_count == 1
|
assert sbs.call_count == 1
|
||||||
assert sbc.call_count == 1
|
assert sbc.call_count == 1
|
||||||
|
|
||||||
|
@ -872,7 +872,8 @@ def test_in_strategy_auto_hyperopt(mocker, hyperopt_conf, tmpdir, fee) -> None:
|
|||||||
hyperopt.backtesting.exchange.get_max_leverage = MagicMock(return_value=1.0)
|
hyperopt.backtesting.exchange.get_max_leverage = MagicMock(return_value=1.0)
|
||||||
assert isinstance(hyperopt.custom_hyperopt, HyperOptAuto)
|
assert isinstance(hyperopt.custom_hyperopt, HyperOptAuto)
|
||||||
assert isinstance(hyperopt.backtesting.strategy.buy_rsi, IntParameter)
|
assert isinstance(hyperopt.backtesting.strategy.buy_rsi, IntParameter)
|
||||||
assert hyperopt.backtesting.strategy.bot_loop_started is True
|
assert hyperopt.backtesting.strategy.bot_started is True
|
||||||
|
assert hyperopt.backtesting.strategy.bot_loop_started is False
|
||||||
|
|
||||||
assert hyperopt.backtesting.strategy.buy_rsi.in_space is True
|
assert hyperopt.backtesting.strategy.buy_rsi.in_space is True
|
||||||
assert hyperopt.backtesting.strategy.buy_rsi.value == 35
|
assert hyperopt.backtesting.strategy.buy_rsi.value == 35
|
||||||
@ -922,7 +923,8 @@ def test_in_strategy_auto_hyperopt_with_parallel(mocker, hyperopt_conf, tmpdir,
|
|||||||
|
|
||||||
assert isinstance(hyperopt.custom_hyperopt, HyperOptAuto)
|
assert isinstance(hyperopt.custom_hyperopt, HyperOptAuto)
|
||||||
assert isinstance(hyperopt.backtesting.strategy.buy_rsi, IntParameter)
|
assert isinstance(hyperopt.backtesting.strategy.buy_rsi, IntParameter)
|
||||||
assert hyperopt.backtesting.strategy.bot_loop_started is True
|
assert hyperopt.backtesting.strategy.bot_started is True
|
||||||
|
assert hyperopt.backtesting.strategy.bot_loop_started is False
|
||||||
|
|
||||||
assert hyperopt.backtesting.strategy.buy_rsi.in_space is True
|
assert hyperopt.backtesting.strategy.buy_rsi.in_space is True
|
||||||
assert hyperopt.backtesting.strategy.buy_rsi.value == 35
|
assert hyperopt.backtesting.strategy.buy_rsi.value == 35
|
||||||
@ -959,7 +961,8 @@ def test_in_strategy_auto_hyperopt_per_epoch(mocker, hyperopt_conf, tmpdir, fee)
|
|||||||
hyperopt.backtesting.exchange.get_max_leverage = MagicMock(return_value=1.0)
|
hyperopt.backtesting.exchange.get_max_leverage = MagicMock(return_value=1.0)
|
||||||
assert isinstance(hyperopt.custom_hyperopt, HyperOptAuto)
|
assert isinstance(hyperopt.custom_hyperopt, HyperOptAuto)
|
||||||
assert isinstance(hyperopt.backtesting.strategy.buy_rsi, IntParameter)
|
assert isinstance(hyperopt.backtesting.strategy.buy_rsi, IntParameter)
|
||||||
assert hyperopt.backtesting.strategy.bot_loop_started is True
|
assert hyperopt.backtesting.strategy.bot_loop_started is False
|
||||||
|
assert hyperopt.backtesting.strategy.bot_started is True
|
||||||
|
|
||||||
assert hyperopt.backtesting.strategy.buy_rsi.in_space is True
|
assert hyperopt.backtesting.strategy.buy_rsi.in_space is True
|
||||||
assert hyperopt.backtesting.strategy.buy_rsi.value == 35
|
assert hyperopt.backtesting.strategy.buy_rsi.value == 35
|
||||||
|
@ -50,6 +50,7 @@ class HyperoptableStrategy(StrategyTestV3):
|
|||||||
return prot
|
return prot
|
||||||
|
|
||||||
bot_loop_started = False
|
bot_loop_started = False
|
||||||
|
bot_started = False
|
||||||
|
|
||||||
def bot_loop_start(self):
|
def bot_loop_start(self):
|
||||||
self.bot_loop_started = True
|
self.bot_loop_started = True
|
||||||
@ -58,6 +59,7 @@ class HyperoptableStrategy(StrategyTestV3):
|
|||||||
"""
|
"""
|
||||||
Parameters can also be defined here ...
|
Parameters can also be defined here ...
|
||||||
"""
|
"""
|
||||||
|
self.bot_started = True
|
||||||
self.buy_rsi = IntParameter([0, 50], default=30, space='buy')
|
self.buy_rsi = IntParameter([0, 50], default=30, space='buy')
|
||||||
|
|
||||||
def informative_pairs(self):
|
def informative_pairs(self):
|
||||||
|
Loading…
Reference in New Issue
Block a user