mirror of
https://github.com/freqtrade/freqtrade.git
synced 2024-11-10 10:21:59 +00:00
Merge pull request #9014 from hippocritical/develop
bugfixes and false-positives for lookahead-analysis
This commit is contained in:
commit
4b8569b80e
|
@ -21,7 +21,10 @@ It also supports the lookahead-analysis of freqai strategies.
|
||||||
|
|
||||||
- `--cache` is forced to "none".
|
- `--cache` is forced to "none".
|
||||||
- `--max-open-trades` is forced to be at least equal to the number of pairs.
|
- `--max-open-trades` is forced to be at least equal to the number of pairs.
|
||||||
- `--dry-run-wallet` is forced to be basically infinite.
|
- `--dry-run-wallet` is forced to be basically infinite (1 billion).
|
||||||
|
- `--stake-amount` is forced to be a static 10000 (10k).
|
||||||
|
|
||||||
|
Those are set to avoid users accidentally generating false positives.
|
||||||
|
|
||||||
## Lookahead-analysis command reference
|
## Lookahead-analysis command reference
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,7 @@ class LookaheadAnalysis:
|
||||||
self.entry_varHolders: List[VarHolder] = []
|
self.entry_varHolders: List[VarHolder] = []
|
||||||
self.exit_varHolders: List[VarHolder] = []
|
self.exit_varHolders: List[VarHolder] = []
|
||||||
self.exchange: Optional[Any] = None
|
self.exchange: Optional[Any] = None
|
||||||
|
self._fee = None
|
||||||
|
|
||||||
# pull variables the scope of the lookahead_analysis-instance
|
# pull variables the scope of the lookahead_analysis-instance
|
||||||
self.local_config = deepcopy(config)
|
self.local_config = deepcopy(config)
|
||||||
|
@ -145,8 +146,13 @@ class LookaheadAnalysis:
|
||||||
str(self.dt_to_timestamp(varholder.to_dt)))
|
str(self.dt_to_timestamp(varholder.to_dt)))
|
||||||
prepare_data_config['exchange']['pair_whitelist'] = pairs_to_load
|
prepare_data_config['exchange']['pair_whitelist'] = pairs_to_load
|
||||||
|
|
||||||
|
if self._fee is not None:
|
||||||
|
# Don't re-calculate fee per pair, as fee might differ per pair.
|
||||||
|
prepare_data_config['fee'] = self._fee
|
||||||
|
|
||||||
backtesting = Backtesting(prepare_data_config, self.exchange)
|
backtesting = Backtesting(prepare_data_config, self.exchange)
|
||||||
self.exchange = backtesting.exchange
|
self.exchange = backtesting.exchange
|
||||||
|
self._fee = backtesting.fee
|
||||||
backtesting._set_strategy(backtesting.strategylist[0])
|
backtesting._set_strategy(backtesting.strategylist[0])
|
||||||
|
|
||||||
varholder.data, varholder.timerange = backtesting.load_bt_data()
|
varholder.data, varholder.timerange = backtesting.load_bt_data()
|
||||||
|
@ -198,7 +204,7 @@ class LookaheadAnalysis:
|
||||||
self.prepare_data(exit_varHolder, [result_row['pair']])
|
self.prepare_data(exit_varHolder, [result_row['pair']])
|
||||||
|
|
||||||
# now we analyze a full trade of full_varholder and look for analyze its bias
|
# now we analyze a full trade of full_varholder and look for analyze its bias
|
||||||
def analyze_row(self, idx, result_row):
|
def analyze_row(self, idx: int, result_row):
|
||||||
# if force-sold, ignore this signal since here it will unconditionally exit.
|
# if force-sold, ignore this signal since here it will unconditionally exit.
|
||||||
if result_row.close_date == self.dt_to_timestamp(self.full_varHolder.to_dt):
|
if result_row.close_date == self.dt_to_timestamp(self.full_varHolder.to_dt):
|
||||||
return
|
return
|
||||||
|
@ -209,12 +215,16 @@ class LookaheadAnalysis:
|
||||||
# fill entry_varHolder and exit_varHolder
|
# fill entry_varHolder and exit_varHolder
|
||||||
self.fill_entry_and_exit_varHolders(result_row)
|
self.fill_entry_and_exit_varHolders(result_row)
|
||||||
|
|
||||||
|
# this will trigger a logger-message
|
||||||
|
buy_or_sell_biased: bool = False
|
||||||
|
|
||||||
# register if buy signal is broken
|
# register if buy signal is broken
|
||||||
if not self.report_signal(
|
if not self.report_signal(
|
||||||
self.entry_varHolders[idx].result,
|
self.entry_varHolders[idx].result,
|
||||||
"open_date",
|
"open_date",
|
||||||
self.entry_varHolders[idx].compared_dt):
|
self.entry_varHolders[idx].compared_dt):
|
||||||
self.current_analysis.false_entry_signals += 1
|
self.current_analysis.false_entry_signals += 1
|
||||||
|
buy_or_sell_biased = True
|
||||||
|
|
||||||
# register if buy or sell signal is broken
|
# register if buy or sell signal is broken
|
||||||
if not self.report_signal(
|
if not self.report_signal(
|
||||||
|
@ -222,6 +232,13 @@ class LookaheadAnalysis:
|
||||||
"close_date",
|
"close_date",
|
||||||
self.exit_varHolders[idx].compared_dt):
|
self.exit_varHolders[idx].compared_dt):
|
||||||
self.current_analysis.false_exit_signals += 1
|
self.current_analysis.false_exit_signals += 1
|
||||||
|
buy_or_sell_biased = True
|
||||||
|
|
||||||
|
if buy_or_sell_biased:
|
||||||
|
logger.info(f"found lookahead-bias in trade "
|
||||||
|
f"pair: {result_row['pair']}, "
|
||||||
|
f"timerange:{result_row['open_date']} - {result_row['close_date']}, "
|
||||||
|
f"idx: {idx}")
|
||||||
|
|
||||||
# check if the indicators themselves contain biased data
|
# check if the indicators themselves contain biased data
|
||||||
self.analyze_indicators(self.full_varHolder, self.entry_varHolders[idx], result_row['pair'])
|
self.analyze_indicators(self.full_varHolder, self.entry_varHolders[idx], result_row['pair'])
|
||||||
|
@ -251,9 +268,33 @@ class LookaheadAnalysis:
|
||||||
# starting from the same datetime to avoid miss-reports of bias
|
# starting from the same datetime to avoid miss-reports of bias
|
||||||
for idx, result_row in self.full_varHolder.result['results'].iterrows():
|
for idx, result_row in self.full_varHolder.result['results'].iterrows():
|
||||||
if self.current_analysis.total_signals == self.targeted_trade_amount:
|
if self.current_analysis.total_signals == self.targeted_trade_amount:
|
||||||
|
logger.info(f"Found targeted trade amount = {self.targeted_trade_amount} signals.")
|
||||||
break
|
break
|
||||||
|
if found_signals < self.minimum_trade_amount:
|
||||||
|
logger.info(f"only found {found_signals} "
|
||||||
|
f"which is smaller than "
|
||||||
|
f"minimum trade amount = {self.minimum_trade_amount}. "
|
||||||
|
f"Exiting this lookahead-analysis")
|
||||||
|
return None
|
||||||
|
if "force_exit" in result_row['exit_reason']:
|
||||||
|
logger.info("found force-exit in pair: {result_row['pair']}, "
|
||||||
|
f"timerange:{result_row['open_date']}-{result_row['close_date']}, "
|
||||||
|
f"idx: {idx}, skipping this one to avoid a false-positive.")
|
||||||
|
|
||||||
|
# just to keep the IDs of both full, entry and exit varholders the same
|
||||||
|
# to achieve a better debugging experience
|
||||||
|
self.entry_varHolders.append(VarHolder())
|
||||||
|
self.exit_varHolders.append(VarHolder())
|
||||||
|
continue
|
||||||
|
|
||||||
self.analyze_row(idx, result_row)
|
self.analyze_row(idx, result_row)
|
||||||
|
|
||||||
|
if len(self.entry_varHolders) < self.minimum_trade_amount:
|
||||||
|
logger.info(f"only found {found_signals} after skipping forced exits "
|
||||||
|
f"which is smaller than "
|
||||||
|
f"minimum trade amount = {self.minimum_trade_amount}. "
|
||||||
|
f"Exiting this lookahead-analysis")
|
||||||
|
|
||||||
# Restore verbosity, so it's not too quiet for the next strategy
|
# Restore verbosity, so it's not too quiet for the next strategy
|
||||||
restore_verbosity_for_bias_tester()
|
restore_verbosity_for_bias_tester()
|
||||||
# check and report signals
|
# check and report signals
|
||||||
|
|
|
@ -137,6 +137,19 @@ class LookaheadAnalysisSubFunctions:
|
||||||
'just to avoid false positives')
|
'just to avoid false positives')
|
||||||
config['dry_run_wallet'] = min_dry_run_wallet
|
config['dry_run_wallet'] = min_dry_run_wallet
|
||||||
|
|
||||||
|
if 'timerange' not in config:
|
||||||
|
# setting a timerange is enforced here
|
||||||
|
raise OperationalException(
|
||||||
|
"Please set a timerange. "
|
||||||
|
"Usually a few months are enough depending on your needs and strategy."
|
||||||
|
)
|
||||||
|
# fix stake_amount to 10k.
|
||||||
|
# in a combination with a wallet size of 1 billion it should always be able to trade
|
||||||
|
# no matter if they use custom_stake_amount as a small percentage of wallet size
|
||||||
|
# or fixate custom_stake_amount to a certain value.
|
||||||
|
logger.info('fixing stake_amount to 10k')
|
||||||
|
config['stake_amount'] = 10000
|
||||||
|
|
||||||
# enforce cache to be 'none', shift it to 'none' if not already
|
# enforce cache to be 'none', shift it to 'none' if not already
|
||||||
# (since the default value is 'day')
|
# (since the default value is 'day')
|
||||||
if config.get('backtest_cache') is None:
|
if config.get('backtest_cache') is None:
|
||||||
|
|
|
@ -17,6 +17,8 @@ from tests.conftest import EXMS, get_args, log_has_re, patch_exchange
|
||||||
def lookahead_conf(default_conf_usdt):
|
def lookahead_conf(default_conf_usdt):
|
||||||
default_conf_usdt['minimum_trade_amount'] = 10
|
default_conf_usdt['minimum_trade_amount'] = 10
|
||||||
default_conf_usdt['targeted_trade_amount'] = 20
|
default_conf_usdt['targeted_trade_amount'] = 20
|
||||||
|
default_conf_usdt['timerange'] = '20220101-20220501'
|
||||||
|
|
||||||
default_conf_usdt['strategy_path'] = str(
|
default_conf_usdt['strategy_path'] = str(
|
||||||
Path(__file__).parent.parent / "strategy/strats/lookahead_bias")
|
Path(__file__).parent.parent / "strategy/strats/lookahead_bias")
|
||||||
default_conf_usdt['strategy'] = 'strategy_test_v3_with_lookahead_bias'
|
default_conf_usdt['strategy'] = 'strategy_test_v3_with_lookahead_bias'
|
||||||
|
@ -43,7 +45,9 @@ def test_start_lookahead_analysis(mocker):
|
||||||
"--pairs",
|
"--pairs",
|
||||||
"UNITTEST/BTC",
|
"UNITTEST/BTC",
|
||||||
"--max-open-trades",
|
"--max-open-trades",
|
||||||
"1"
|
"1",
|
||||||
|
"--timerange",
|
||||||
|
"20220101-20220201"
|
||||||
]
|
]
|
||||||
pargs = get_args(args)
|
pargs = get_args(args)
|
||||||
pargs['config'] = None
|
pargs['config'] = None
|
||||||
|
@ -72,6 +76,24 @@ def test_start_lookahead_analysis(mocker):
|
||||||
match=r"Targeted trade amount can't be smaller than minimum trade amount.*"):
|
match=r"Targeted trade amount can't be smaller than minimum trade amount.*"):
|
||||||
start_lookahead_analysis(pargs)
|
start_lookahead_analysis(pargs)
|
||||||
|
|
||||||
|
# Missing timerange
|
||||||
|
args = [
|
||||||
|
"lookahead-analysis",
|
||||||
|
"--strategy",
|
||||||
|
"strategy_test_v3_with_lookahead_bias",
|
||||||
|
"--strategy-path",
|
||||||
|
str(Path(__file__).parent.parent / "strategy/strats/lookahead_bias"),
|
||||||
|
"--pairs",
|
||||||
|
"UNITTEST/BTC",
|
||||||
|
"--max-open-trades",
|
||||||
|
"1",
|
||||||
|
]
|
||||||
|
pargs = get_args(args)
|
||||||
|
pargs['config'] = None
|
||||||
|
with pytest.raises(OperationalException,
|
||||||
|
match=r"Please set a timerange\..*"):
|
||||||
|
start_lookahead_analysis(pargs)
|
||||||
|
|
||||||
|
|
||||||
def test_lookahead_helper_invalid_config(lookahead_conf) -> None:
|
def test_lookahead_helper_invalid_config(lookahead_conf) -> None:
|
||||||
conf = deepcopy(lookahead_conf)
|
conf = deepcopy(lookahead_conf)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user