Merge branch 'develop' into backtest_fitlivepredictions

This commit is contained in:
Wagner Costa 2022-11-30 08:29:28 -03:00
commit 26e8a5766f
10 changed files with 118 additions and 60 deletions

View File

@ -100,3 +100,17 @@ freqtrade backtesting-analysis -c <config.json> --analysis-groups 0 2 --enter-re
The indicators have to be present in your strategy's main DataFrame (either for your main The indicators have to be present in your strategy's main DataFrame (either for your main
timeframe or for informative timeframes) otherwise they will simply be ignored in the script timeframe or for informative timeframes) otherwise they will simply be ignored in the script
output. output.
### Filtering the trade output by date
To show only trades between dates within your backtested timerange, supply the usual `timerange` option in `YYYYMMDD-[YYYYMMDD]` format:
```
--timerange : Timerange to filter output trades, start date inclusive, end date exclusive. e.g. 20220101-20221231
```
For example, if your backtest timerange was `20220101-20221231` but you only want to output trades in January:
```bash
freqtrade backtesting-analysis -c <config.json> --timerange 20220101-20220201
```

View File

@ -83,7 +83,7 @@ from pathlib import Path
project_root = "somedir/freqtrade" project_root = "somedir/freqtrade"
i=0 i=0
try: try:
os.chdirdir(project_root) os.chdir(project_root)
assert Path('LICENSE').is_file() assert Path('LICENSE').is_file()
except: except:
while i<4 and (not Path('LICENSE').is_file()): while i<4 and (not Path('LICENSE').is_file()):

View File

@ -722,6 +722,7 @@ usage: freqtrade backtesting-analysis [-h] [-v] [--logfile FILE] [-V]
[--enter-reason-list ENTER_REASON_LIST [ENTER_REASON_LIST ...]] [--enter-reason-list ENTER_REASON_LIST [ENTER_REASON_LIST ...]]
[--exit-reason-list EXIT_REASON_LIST [EXIT_REASON_LIST ...]] [--exit-reason-list EXIT_REASON_LIST [EXIT_REASON_LIST ...]]
[--indicator-list INDICATOR_LIST [INDICATOR_LIST ...]] [--indicator-list INDICATOR_LIST [INDICATOR_LIST ...]]
[--timerange YYYYMMDD-[YYYYMMDD]]
optional arguments: optional arguments:
-h, --help show this help message and exit -h, --help show this help message and exit
@ -744,6 +745,10 @@ optional arguments:
--indicator-list INDICATOR_LIST [INDICATOR_LIST ...] --indicator-list INDICATOR_LIST [INDICATOR_LIST ...]
Comma separated list of indicators to analyse. e.g. Comma separated list of indicators to analyse. e.g.
'close,rsi,bb_lowerband,profit_abs' 'close,rsi,bb_lowerband,profit_abs'
--timerange YYYYMMDD-[YYYYMMDD]
Timerange to filter trades for analysis,
start inclusive, end exclusive. e.g.
20220101-20220201
Common arguments: Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages). -v, --verbose Verbose mode (-vv for more, -vvv to get all messages).

View File

@ -60,10 +60,4 @@ def start_analysis_entries_exits(args: Dict[str, Any]) -> None:
logger.info('Starting freqtrade in analysis mode') logger.info('Starting freqtrade in analysis mode')
process_entry_exit_reasons(config['exportfilename'], process_entry_exit_reasons(config)
config['exchange']['pair_whitelist'],
config['analysis_groups'],
config['enter_reason_list'],
config['exit_reason_list'],
config['indicator_list']
)

View File

@ -106,7 +106,7 @@ ARGS_HYPEROPT_SHOW = ["hyperopt_list_best", "hyperopt_list_profitable", "hyperop
"disableparamexport", "backtest_breakdown"] "disableparamexport", "backtest_breakdown"]
ARGS_ANALYZE_ENTRIES_EXITS = ["exportfilename", "analysis_groups", "enter_reason_list", ARGS_ANALYZE_ENTRIES_EXITS = ["exportfilename", "analysis_groups", "enter_reason_list",
"exit_reason_list", "indicator_list"] "exit_reason_list", "indicator_list", "timerange"]
NO_CONF_REQURIED = ["convert-data", "convert-trade-data", "download-data", "list-timeframes", NO_CONF_REQURIED = ["convert-data", "convert-trade-data", "download-data", "list-timeframes",
"list-markets", "list-pairs", "list-strategies", "list-freqaimodels", "list-markets", "list-pairs", "list-strategies", "list-freqaimodels",

View File

@ -462,6 +462,9 @@ class Configuration:
self._args_to_config(config, argname='indicator_list', self._args_to_config(config, argname='indicator_list',
logstring='Analysis indicator list: {}') logstring='Analysis indicator list: {}')
self._args_to_config(config, argname='timerange',
logstring='Filter trades by timerange: {}')
def _process_runmode(self, config: Config) -> None: def _process_runmode(self, config: Config) -> None:
self._args_to_config(config, argname='dry_run', self._args_to_config(config, argname='dry_run',

View File

@ -1,11 +1,12 @@
import logging import logging
from pathlib import Path from pathlib import Path
from typing import List, Optional
import joblib import joblib
import pandas as pd import pandas as pd
from tabulate import tabulate from tabulate import tabulate
from freqtrade.configuration import TimeRange
from freqtrade.constants import Config
from freqtrade.data.btanalysis import (get_latest_backtest_filename, load_backtest_data, from freqtrade.data.btanalysis import (get_latest_backtest_filename, load_backtest_data,
load_backtest_stats) load_backtest_stats)
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
@ -152,37 +153,55 @@ def _do_group_table_output(bigdf, glist):
logger.warning("Invalid group mask specified.") logger.warning("Invalid group mask specified.")
def _print_results(analysed_trades, stratname, analysis_groups, def _select_rows_within_dates(df, timerange=None, df_date_col: str = 'date'):
enter_reason_list, exit_reason_list, if timerange:
indicator_list, columns=None): if timerange.starttype == 'date':
if columns is None: df = df.loc[(df[df_date_col] >= timerange.startdt)]
columns = ['pair', 'open_date', 'close_date', 'profit_abs', 'enter_reason', 'exit_reason'] if timerange.stoptype == 'date':
df = df.loc[(df[df_date_col] < timerange.stopdt)]
return df
bigdf = pd.DataFrame()
def _select_rows_by_tags(df, enter_reason_list, exit_reason_list):
if enter_reason_list and "all" not in enter_reason_list:
df = df.loc[(df['enter_reason'].isin(enter_reason_list))]
if exit_reason_list and "all" not in exit_reason_list:
df = df.loc[(df['exit_reason'].isin(exit_reason_list))]
return df
def prepare_results(analysed_trades, stratname,
enter_reason_list, exit_reason_list,
timerange=None):
res_df = pd.DataFrame()
for pair, trades in analysed_trades[stratname].items(): for pair, trades in analysed_trades[stratname].items():
bigdf = pd.concat([bigdf, trades], ignore_index=True) res_df = pd.concat([res_df, trades], ignore_index=True)
if bigdf.shape[0] > 0 and ('enter_reason' in bigdf.columns): res_df = _select_rows_within_dates(res_df, timerange)
if res_df is not None and res_df.shape[0] > 0 and ('enter_reason' in res_df.columns):
res_df = _select_rows_by_tags(res_df, enter_reason_list, exit_reason_list)
return res_df
def print_results(res_df, analysis_groups, indicator_list):
if res_df.shape[0] > 0:
if analysis_groups: if analysis_groups:
_do_group_table_output(bigdf, analysis_groups) _do_group_table_output(res_df, analysis_groups)
if enter_reason_list and "all" not in enter_reason_list:
bigdf = bigdf.loc[(bigdf['enter_reason'].isin(enter_reason_list))]
if exit_reason_list and "all" not in exit_reason_list:
bigdf = bigdf.loc[(bigdf['exit_reason'].isin(exit_reason_list))]
if "all" in indicator_list: if "all" in indicator_list:
print(bigdf) print(res_df)
elif indicator_list is not None: elif indicator_list is not None:
available_inds = [] available_inds = []
for ind in indicator_list: for ind in indicator_list:
if ind in bigdf: if ind in res_df:
available_inds.append(ind) available_inds.append(ind)
ilist = ["pair", "enter_reason", "exit_reason"] + available_inds ilist = ["pair", "enter_reason", "exit_reason"] + available_inds
_print_table(bigdf[ilist], sortcols=['exit_reason'], show_index=False) _print_table(res_df[ilist], sortcols=['exit_reason'], show_index=False)
else: else:
print("\\_ No trades to show") print("\\No trades to show")
def _print_table(df, sortcols=None, show_index=False): def _print_table(df, sortcols=None, show_index=False):
@ -201,27 +220,34 @@ def _print_table(df, sortcols=None, show_index=False):
) )
def process_entry_exit_reasons(backtest_dir: Path, def process_entry_exit_reasons(config: Config):
pairlist: List[str],
analysis_groups: Optional[List[str]] = ["0", "1", "2"],
enter_reason_list: Optional[List[str]] = ["all"],
exit_reason_list: Optional[List[str]] = ["all"],
indicator_list: Optional[List[str]] = []):
try: try:
backtest_stats = load_backtest_stats(backtest_dir) analysis_groups = config.get('analysis_groups', [])
enter_reason_list = config.get('enter_reason_list', ["all"])
exit_reason_list = config.get('exit_reason_list', ["all"])
indicator_list = config.get('indicator_list', [])
timerange = TimeRange.parse_timerange(None if config.get(
'timerange') is None else str(config.get('timerange')))
backtest_stats = load_backtest_stats(config['exportfilename'])
for strategy_name, results in backtest_stats['strategy'].items(): for strategy_name, results in backtest_stats['strategy'].items():
trades = load_backtest_data(backtest_dir, strategy_name) trades = load_backtest_data(config['exportfilename'], strategy_name)
if not trades.empty: if not trades.empty:
signal_candles = _load_signal_candles(backtest_dir) signal_candles = _load_signal_candles(config['exportfilename'])
analysed_trades_dict = _process_candles_and_indicators(pairlist, strategy_name, analysed_trades_dict = _process_candles_and_indicators(
trades, signal_candles) config['exchange']['pair_whitelist'], strategy_name,
_print_results(analysed_trades_dict, trades, signal_candles)
strategy_name,
analysis_groups, res_df = prepare_results(analysed_trades_dict, strategy_name,
enter_reason_list, enter_reason_list, exit_reason_list,
exit_reason_list, timerange=timerange)
indicator_list)
print_results(res_df,
analysis_groups,
indicator_list)
except ValueError as e: except ValueError as e:
raise OperationalException(e) from e raise OperationalException(e) from e

View File

@ -740,6 +740,24 @@ class RPC:
self._freqtrade.wallets.update() self._freqtrade.wallets.update()
return {'result': f'Created sell order for trade {trade_id}.'} return {'result': f'Created sell order for trade {trade_id}.'}
def _force_entry_validations(self, pair: str, order_side: SignalDirection):
if not self._freqtrade.config.get('force_entry_enable', False):
raise RPCException('Force_entry not enabled.')
if self._freqtrade.state != State.RUNNING:
raise RPCException('trader is not running')
if order_side == SignalDirection.SHORT and self._freqtrade.trading_mode == TradingMode.SPOT:
raise RPCException("Can't go short on Spot markets.")
if pair not in self._freqtrade.exchange.get_markets(tradable_only=True):
raise RPCException('Symbol does not exist or market is not active.')
# Check if pair quote currency equals to the stake currency.
stake_currency = self._freqtrade.config.get('stake_currency')
if not self._freqtrade.exchange.get_pair_quote_currency(pair) == stake_currency:
raise RPCException(
f'Wrong pair selected. Only pairs with stake-currency {stake_currency} allowed.')
def _rpc_force_entry(self, pair: str, price: Optional[float], *, def _rpc_force_entry(self, pair: str, price: Optional[float], *,
order_type: Optional[str] = None, order_type: Optional[str] = None,
order_side: SignalDirection = SignalDirection.LONG, order_side: SignalDirection = SignalDirection.LONG,
@ -750,21 +768,8 @@ class RPC:
Handler for forcebuy <asset> <price> Handler for forcebuy <asset> <price>
Buys a pair trade at the given or current price Buys a pair trade at the given or current price
""" """
self._force_entry_validations(pair, order_side)
if not self._freqtrade.config.get('force_entry_enable', False):
raise RPCException('Force_entry not enabled.')
if self._freqtrade.state != State.RUNNING:
raise RPCException('trader is not running')
if order_side == SignalDirection.SHORT and self._freqtrade.trading_mode == TradingMode.SPOT:
raise RPCException("Can't go short on Spot markets.")
# Check if pair quote currency equals to the stake currency.
stake_currency = self._freqtrade.config.get('stake_currency')
if not self._freqtrade.exchange.get_pair_quote_currency(pair) == stake_currency:
raise RPCException(
f'Wrong pair selected. Only pairs with stake-currency {stake_currency} allowed.')
# check if valid pair # check if valid pair
# check if pair already has an open pair # check if pair already has an open pair

View File

@ -189,3 +189,10 @@ def test_backtest_analysis_nomock(default_conf, mocker, caplog, testdatadir, tmp
assert '0.5' in captured.out assert '0.5' in captured.out
assert '1' in captured.out assert '1' in captured.out
assert '2.5' in captured.out assert '2.5' in captured.out
# test date filtering
args = get_args(base_args + ['--timerange', "20180129-20180130"])
start_analysis_entries_exits(args)
captured = capsys.readouterr()
assert 'enter_tag_long_a' in captured.out
assert 'enter_tag_long_b' not in captured.out

View File

@ -1056,6 +1056,10 @@ def test_rpc_force_entry(mocker, default_conf, ticker, fee, limit_buy_order_open
assert trade.pair == pair assert trade.pair == pair
assert trade.open_rate == 0.0001 assert trade.open_rate == 0.0001
with pytest.raises(RPCException,
match=r'Symbol does not exist or market is not active.'):
rpc._rpc_force_entry('LTC/NOTHING', 0.0001)
# Test buy pair not with stakes # Test buy pair not with stakes
with pytest.raises(RPCException, with pytest.raises(RPCException,
match=r'Wrong pair selected. Only pairs with stake-currency.*'): match=r'Wrong pair selected. Only pairs with stake-currency.*'):