Merge pull request #2344 from freqtrade/backtest_nofees

Backtest no fees / custom fees
This commit is contained in:
hroff-1902 2019-10-07 13:30:20 +03:00 committed by GitHub
commit edfbb56749
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 118 additions and 60 deletions

View File

@ -72,6 +72,17 @@ The exported trades can be used for [further analysis](#further-backtest-result-
freqtrade backtesting --export trades --export-filename=backtest_samplestrategy.json freqtrade backtesting --export trades --export-filename=backtest_samplestrategy.json
``` ```
#### Supplying custom fee value
Sometimes your account has certain fee rebates (fee reductions starting with a certain account size or monthly volume), which are not visible to ccxt.
To account for this in backtesting, you can use `--fee 0.001` to supply this value to backtesting.
This fee must be a percentage, and will be applied twice (once for trade entry, and once for trade exit).
```bash
freqtrade backtesting --fee 0.001
```
#### Running backtest with smaller testset by using timerange #### Running backtest with smaller testset by using timerange
Use the `--timerange` argument to change how much of the testset you want to use. Use the `--timerange` argument to change how much of the testset you want to use.

View File

@ -174,22 +174,25 @@ Backtesting also uses the config specified via `-c/--config`.
``` ```
usage: freqtrade backtesting [-h] [-i TICKER_INTERVAL] [--timerange TIMERANGE] usage: freqtrade backtesting [-h] [-i TICKER_INTERVAL] [--timerange TIMERANGE]
[--max_open_trades MAX_OPEN_TRADES] [--max_open_trades INT]
[--stake_amount STAKE_AMOUNT] [-r] [--eps] [--dmmp] [--stake_amount STAKE_AMOUNT] [--fee FLOAT]
[-l] [--eps] [--dmmp]
[--strategy-list STRATEGY_LIST [STRATEGY_LIST ...]] [--strategy-list STRATEGY_LIST [STRATEGY_LIST ...]]
[--export EXPORT] [--export-filename PATH] [--export EXPORT] [--export-filename PATH]
optional arguments: optional arguments:
-h, --help show this help message and exit -h, --help show this help message and exit
-i TICKER_INTERVAL, --ticker-interval TICKER_INTERVAL -i TICKER_INTERVAL, --ticker-interval TICKER_INTERVAL
Specify ticker interval (1m, 5m, 30m, 1h, 1d). Specify ticker interval (`1m`, `5m`, `30m`, `1h`,
`1d`).
--timerange TIMERANGE --timerange TIMERANGE
Specify what timerange of data to use. Specify what timerange of data to use.
--max_open_trades MAX_OPEN_TRADES --max_open_trades INT
Specify max_open_trades to use. Specify max_open_trades to use.
--stake_amount STAKE_AMOUNT --stake_amount STAKE_AMOUNT
Specify stake_amount. Specify stake_amount.
--fee FLOAT Specify fee ratio. Will be applied twice (on trade
entry and exit).
--eps, --enable-position-stacking --eps, --enable-position-stacking
Allow buying the same pair multiple times (position Allow buying the same pair multiple times (position
stacking). stacking).
@ -199,19 +202,21 @@ optional arguments:
number). number).
--strategy-list STRATEGY_LIST [STRATEGY_LIST ...] --strategy-list STRATEGY_LIST [STRATEGY_LIST ...]
Provide a space-separated list of strategies to Provide a space-separated list of strategies to
backtest Please note that ticker-interval needs to be backtest. Please note that ticker-interval needs to be
set either in config or via command line. When using set either in config or via command line. When using
this together with --export trades, the strategy-name this together with `--export trades`, the strategy-
is injected into the filename (so backtest-data.json name is injected into the filename (so `backtest-
becomes backtest-data-DefaultStrategy.json data.json` becomes `backtest-data-
--export EXPORT Export backtest results, argument are: trades. Example DefaultStrategy.json`
--export=trades --export EXPORT Export backtest results, argument are: trades.
Example: `--export=trades`
--export-filename PATH --export-filename PATH
Save backtest results to this filename requires Save backtest results to the file with this filename
--export to be set as well Example --export- (default: `user_data/backtest_results/backtest-
filename=user_data/backtest_results/backtest_today.json result.json`). Requires `--export` to be set as well.
(default: user_data/backtest_results/backtest- Example: `--export-filename=user_data/backtest_results
result.json) /backtest_today.json`
``` ```
### Getting historic data for backtesting ### Getting historic data for backtesting
@ -228,13 +233,13 @@ to find optimal parameter values for your stategy.
``` ```
usage: freqtrade hyperopt [-h] [-i TICKER_INTERVAL] [--timerange TIMERANGE] usage: freqtrade hyperopt [-h] [-i TICKER_INTERVAL] [--timerange TIMERANGE]
[--max_open_trades INT] [--max_open_trades INT]
[--stake_amount STAKE_AMOUNT] [-r] [--stake_amount STAKE_AMOUNT] [--fee FLOAT]
[--customhyperopt NAME] [--hyperopt-path PATH] [--customhyperopt NAME] [--hyperopt-path PATH]
[--eps] [-e INT] [--eps] [-e INT]
[-s {all,buy,sell,roi,stoploss} [{all,buy,sell,roi,stoploss} ...]] [-s {all,buy,sell,roi,stoploss} [{all,buy,sell,roi,stoploss} ...]]
[--dmmp] [--print-all] [--no-color] [-j JOBS] [--dmmp] [--print-all] [--no-color] [--print-json]
[--random-state INT] [--min-trades INT] [--continue] [-j JOBS] [--random-state INT] [--min-trades INT]
[--hyperopt-loss NAME] [--continue] [--hyperopt-loss NAME]
optional arguments: optional arguments:
-h, --help show this help message and exit -h, --help show this help message and exit
@ -247,6 +252,8 @@ optional arguments:
Specify max_open_trades to use. Specify max_open_trades to use.
--stake_amount STAKE_AMOUNT --stake_amount STAKE_AMOUNT
Specify stake_amount. Specify stake_amount.
--fee FLOAT Specify fee ratio. Will be applied twice (on trade
entry and exit).
--customhyperopt NAME --customhyperopt NAME
Specify hyperopt class name (default: Specify hyperopt class name (default:
`DefaultHyperOpts`). `DefaultHyperOpts`).
@ -266,6 +273,7 @@ optional arguments:
--print-all Print all results, not only the best ones. --print-all Print all results, not only the best ones.
--no-color Disable colorization of hyperopt results. May be --no-color Disable colorization of hyperopt results. May be
useful if you are redirecting output to a file. useful if you are redirecting output to a file.
--print-json Print best result detailization in JSON format.
-j JOBS, --job-workers JOBS -j JOBS, --job-workers JOBS
The number of concurrently running jobs for The number of concurrently running jobs for
hyperoptimization (hyperopt worker processes). If -1 hyperoptimization (hyperopt worker processes). If -1
@ -284,8 +292,8 @@ optional arguments:
generate completely different results, since the generate completely different results, since the
target for optimization is different. Built-in target for optimization is different. Built-in
Hyperopt-loss-functions are: DefaultHyperOptLoss, Hyperopt-loss-functions are: DefaultHyperOptLoss,
OnlyProfitHyperOptLoss, SharpeHyperOptLoss. OnlyProfitHyperOptLoss, SharpeHyperOptLoss.(default:
(default: `DefaultHyperOptLoss`). `DefaultHyperOptLoss`).
``` ```
## Edge commands ## Edge commands
@ -294,25 +302,28 @@ To know your trade expectancy and winrate against historical data, you can use E
``` ```
usage: freqtrade edge [-h] [-i TICKER_INTERVAL] [--timerange TIMERANGE] usage: freqtrade edge [-h] [-i TICKER_INTERVAL] [--timerange TIMERANGE]
[--max_open_trades MAX_OPEN_TRADES] [--max_open_trades INT] [--stake_amount STAKE_AMOUNT]
[--stake_amount STAKE_AMOUNT] [-r] [--fee FLOAT] [--stoplosses STOPLOSS_RANGE]
[--stoplosses STOPLOSS_RANGE]
optional arguments: optional arguments:
-h, --help show this help message and exit -h, --help show this help message and exit
-i TICKER_INTERVAL, --ticker-interval TICKER_INTERVAL -i TICKER_INTERVAL, --ticker-interval TICKER_INTERVAL
Specify ticker interval (1m, 5m, 30m, 1h, 1d). Specify ticker interval (`1m`, `5m`, `30m`, `1h`,
`1d`).
--timerange TIMERANGE --timerange TIMERANGE
Specify what timerange of data to use. Specify what timerange of data to use.
--max_open_trades MAX_OPEN_TRADES --max_open_trades INT
Specify max_open_trades to use. Specify max_open_trades to use.
--stake_amount STAKE_AMOUNT --stake_amount STAKE_AMOUNT
Specify stake_amount. Specify stake_amount.
--fee FLOAT Specify fee ratio. Will be applied twice (on trade
entry and exit).
--stoplosses STOPLOSS_RANGE --stoplosses STOPLOSS_RANGE
Defines a range of stoploss against which edge will Defines a range of stoploss values against which edge
assess the strategy the format is "min,max,step" will assess the strategy. The format is "min,max,step"
(without any space).example: (without any space). Example:
--stoplosses=-0.01,-0.1,-0.001 `--stoplosses=-0.01,-0.1,-0.001`
``` ```
To understand edge and how to read the results, please read the [edge documentation](edge.md). To understand edge and how to read the results, please read the [edge documentation](edge.md).

View File

@ -15,7 +15,7 @@ ARGS_STRATEGY = ["strategy", "strategy_path"]
ARGS_MAIN = ARGS_COMMON + ARGS_STRATEGY + ["db_url", "sd_notify"] ARGS_MAIN = ARGS_COMMON + ARGS_STRATEGY + ["db_url", "sd_notify"]
ARGS_COMMON_OPTIMIZE = ["ticker_interval", "timerange", ARGS_COMMON_OPTIMIZE = ["ticker_interval", "timerange",
"max_open_trades", "stake_amount"] "max_open_trades", "stake_amount", "fee"]
ARGS_BACKTEST = ARGS_COMMON_OPTIMIZE + ["position_stacking", "use_max_market_positions", ARGS_BACKTEST = ARGS_COMMON_OPTIMIZE + ["position_stacking", "use_max_market_positions",
"strategy_list", "export", "exportfilename"] "strategy_list", "export", "exportfilename"]

View File

@ -144,6 +144,12 @@ AVAILABLE_CLI_OPTIONS = {
default=os.path.join('user_data', 'backtest_results', default=os.path.join('user_data', 'backtest_results',
'backtest-result.json'), 'backtest-result.json'),
), ),
"fee": Arg(
'--fee',
help='Specify fee ratio. Will be applied twice (on trade entry and exit).',
type=float,
metavar='FLOAT',
),
# Edge # Edge
"stoploss_range": Arg( "stoploss_range": Arg(
'--stoplosses', '--stoplosses',

View File

@ -210,6 +210,10 @@ class Configuration:
logstring='Parameter --stake_amount detected, ' logstring='Parameter --stake_amount detected, '
'overriding stake_amount to: {} ...') 'overriding stake_amount to: {} ...')
self._args_to_config(config, argname='fee',
logstring='Parameter --fee detected, '
'setting fee to: {} ...')
self._args_to_config(config, argname='timerange', self._args_to_config(config, argname='timerange',
logstring='Parameter --timerange detected: {} ...') logstring='Parameter --timerange detected: {} ...')
@ -323,7 +327,8 @@ class Configuration:
sample: logfun=len (prints the length of the found sample: logfun=len (prints the length of the found
configuration instead of the content) configuration instead of the content)
""" """
if argname in self.args and self.args[argname]: if (argname in self.args and self.args[argname] is not None
and self.args[argname] is not False):
config.update({argname: self.args[argname]}) config.update({argname: self.args[argname]})
if logfun: if logfun:

View File

@ -77,7 +77,9 @@ class Edge:
self._timerange: TimeRange = TimeRange.parse_timerange("%s-" % arrow.now().shift( self._timerange: TimeRange = TimeRange.parse_timerange("%s-" % arrow.now().shift(
days=-1 * self._since_number_of_days).format('YYYYMMDD')) days=-1 * self._since_number_of_days).format('YYYYMMDD'))
if config.get('fee'):
self.fee = config['fee']
else:
self.fee = self.exchange.get_fee() self.fee = self.exchange.get_fee()
def calculate(self) -> bool: def calculate(self) -> bool:

View File

@ -63,8 +63,11 @@ class Backtesting:
self.config['exchange']['uid'] = '' self.config['exchange']['uid'] = ''
self.config['dry_run'] = True self.config['dry_run'] = True
self.strategylist: List[IStrategy] = [] self.strategylist: List[IStrategy] = []
self.exchange = ExchangeResolver(self.config['exchange']['name'], self.config).exchange self.exchange = ExchangeResolver(self.config['exchange']['name'], self.config).exchange
if config.get('fee'):
self.fee = config['fee']
else:
self.fee = self.exchange.get_fee() self.fee = self.exchange.get_fee()
if self.config.get('runmode') != RunMode.HYPEROPT: if self.config.get('runmode') != RunMode.HYPEROPT:

View File

@ -26,6 +26,21 @@ from tests.conftest import (get_args, log_has, log_has_re, patch_exchange,
patched_configuration_load_config_file) patched_configuration_load_config_file)
ORDER_TYPES = [
{
'buy': 'limit',
'sell': 'limit',
'stoploss': 'limit',
'stoploss_on_exchange': False
},
{
'buy': 'limit',
'sell': 'limit',
'stoploss': 'limit',
'stoploss_on_exchange': True
}]
def trim_dictlist(dict_list, num): def trim_dictlist(dict_list, num):
new = {} new = {}
for pair, pair_data in dict_list.items(): for pair, pair_data in dict_list.items():
@ -211,7 +226,8 @@ def test_setup_bt_configuration_with_arguments(mocker, default_conf, caplog) ->
'--disable-max-market-positions', '--disable-max-market-positions',
'--timerange', ':100', '--timerange', ':100',
'--export', '/bar/foo', '--export', '/bar/foo',
'--export-filename', 'foo_bar.json' '--export-filename', 'foo_bar.json',
'--fee', '0',
] ]
config = setup_configuration(get_args(args), RunMode.BACKTEST) config = setup_configuration(get_args(args), RunMode.BACKTEST)
@ -243,6 +259,9 @@ def test_setup_bt_configuration_with_arguments(mocker, default_conf, caplog) ->
assert 'exportfilename' in config assert 'exportfilename' in config
assert log_has('Storing backtest results to {} ...'.format(config['exportfilename']), caplog) assert log_has('Storing backtest results to {} ...'.format(config['exportfilename']), caplog)
assert 'fee' in config
assert log_has('Parameter --fee detected, setting fee to: {} ...'.format(config['fee']), caplog)
def test_setup_configuration_unlimited_stake_amount(mocker, default_conf, caplog) -> None: def test_setup_configuration_unlimited_stake_amount(mocker, default_conf, caplog) -> None:
default_conf['stake_amount'] = constants.UNLIMITED_STAKE_AMOUNT default_conf['stake_amount'] = constants.UNLIMITED_STAKE_AMOUNT
@ -277,21 +296,6 @@ def test_start(mocker, fee, default_conf, caplog) -> None:
assert start_mock.call_count == 1 assert start_mock.call_count == 1
ORDER_TYPES = [
{
'buy': 'limit',
'sell': 'limit',
'stoploss': 'limit',
'stoploss_on_exchange': False
},
{
'buy': 'limit',
'sell': 'limit',
'stoploss': 'limit',
'stoploss_on_exchange': True
}]
@pytest.mark.parametrize("order_types", ORDER_TYPES) @pytest.mark.parametrize("order_types", ORDER_TYPES)
def test_backtesting_init(mocker, default_conf, order_types) -> None: def test_backtesting_init(mocker, default_conf, order_types) -> None:
""" """
@ -314,10 +318,6 @@ def test_backtesting_init(mocker, default_conf, order_types) -> None:
def test_backtesting_init_no_ticker_interval(mocker, default_conf, caplog) -> None: def test_backtesting_init_no_ticker_interval(mocker, default_conf, caplog) -> None:
"""
Check that stoploss_on_exchange is set to False while backtesting
since backtesting assumes a perfect stoploss anyway.
"""
patch_exchange(mocker) patch_exchange(mocker)
del default_conf['ticker_interval'] del default_conf['ticker_interval']
default_conf['strategy_list'] = ['DefaultStrategy', default_conf['strategy_list'] = ['DefaultStrategy',
@ -330,6 +330,16 @@ def test_backtesting_init_no_ticker_interval(mocker, default_conf, caplog) -> No
"or as cli argument `--ticker-interval 5m`", caplog) "or as cli argument `--ticker-interval 5m`", caplog)
def test_tickerdata_with_fee(default_conf, mocker, testdatadir) -> None:
patch_exchange(mocker)
default_conf['fee'] = 0.1234
fee_mock = mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.5))
backtesting = Backtesting(default_conf)
assert backtesting.fee == 0.1234
assert fee_mock.call_count == 0
def test_tickerdata_to_dataframe_bt(default_conf, mocker, testdatadir) -> None: def test_tickerdata_to_dataframe_bt(default_conf, mocker, testdatadir) -> None:
patch_exchange(mocker) patch_exchange(mocker)
timerange = TimeRange(None, 'line', 0, -100) timerange = TimeRange(None, 'line', 0, -100)

View File

@ -98,6 +98,16 @@ def test_edge_init(mocker, edge_conf) -> None:
assert callable(edge_cli.edge.calculate) assert callable(edge_cli.edge.calculate)
def test_edge_init_fee(mocker, edge_conf) -> None:
patch_exchange(mocker)
edge_conf['fee'] = 0.1234
edge_conf['stake_amount'] = 20
fee_mock = mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.5))
edge_cli = EdgeCli(edge_conf)
assert edge_cli.edge.fee == 0.1234
assert fee_mock.call_count == 0
def test_generate_edge_table(edge_conf, mocker): def test_generate_edge_table(edge_conf, mocker):
patch_exchange(mocker) patch_exchange(mocker)
edge_cli = EdgeCli(edge_conf) edge_cli = EdgeCli(edge_conf)

View File

@ -403,7 +403,7 @@ def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> Non
assert log_has('Parameter -i/--ticker-interval detected ... Using ticker_interval: 1m ...', assert log_has('Parameter -i/--ticker-interval detected ... Using ticker_interval: 1m ...',
caplog) caplog)
assert 'position_stacking'in config assert 'position_stacking' in config
assert log_has('Parameter --enable-position-stacking detected ...', caplog) assert log_has('Parameter --enable-position-stacking detected ...', caplog)
assert 'use_max_market_positions' in config assert 'use_max_market_positions' in config