Merge branch 'feat/short' into funding-fee-backtesting

This commit is contained in:
Sam Germain 2021-10-22 11:50:49 -06:00
commit b1a270a53d
18 changed files with 260 additions and 198 deletions

View File

@ -21,6 +21,7 @@ usage: freqtrade backtesting [-h] [-v] [--logfile FILE] [-V] [-c PATH]
[--timeframe-detail TIMEFRAME_DETAIL] [--timeframe-detail TIMEFRAME_DETAIL]
[--strategy-list STRATEGY_LIST [STRATEGY_LIST ...]] [--strategy-list STRATEGY_LIST [STRATEGY_LIST ...]]
[--export {none,trades}] [--export-filename PATH] [--export {none,trades}] [--export-filename PATH]
[--breakdown {day,week,month} [{day,week,month} ...]]
optional arguments: optional arguments:
-h, --help show this help message and exit -h, --help show this help message and exit
@ -30,7 +31,7 @@ optional arguments:
Specify what timerange of data to use. Specify what timerange of data to use.
--data-format-ohlcv {json,jsongz,hdf5} --data-format-ohlcv {json,jsongz,hdf5}
Storage format for downloaded candle (OHLCV) data. Storage format for downloaded candle (OHLCV) data.
(default: `None`). (default: `json`).
--max-open-trades INT --max-open-trades INT
Override the value of the `max_open_trades` Override the value of the `max_open_trades`
configuration setting. configuration setting.
@ -65,8 +66,7 @@ optional arguments:
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- this together with `--export trades`, the strategy-
name is injected into the filename (so `backtest- name is injected into the filename (so `backtest-
data.json` becomes `backtest-data- data.json` becomes `backtest-data-SampleStrategy.json`
SampleStrategy.json`
--export {none,trades} --export {none,trades}
Export backtest results (default: trades). Export backtest results (default: trades).
--export-filename PATH --export-filename PATH
@ -74,6 +74,8 @@ optional arguments:
Requires `--export` to be set as well. Example: Requires `--export` to be set as well. Example:
`--export-filename=user_data/backtest_results/backtest `--export-filename=user_data/backtest_results/backtest
_today.json` _today.json`
--breakdown {day,week,month} [{day,week,month} ...]
Show backtesting breakdown per [day, week, month].
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).
@ -429,6 +431,31 @@ It contains some useful key metrics about performance of your strategy on backte
- `Drawdown Start` / `Drawdown End`: Start and end datetime for this largest drawdown (can also be visualized via the `plot-dataframe` sub-command). - `Drawdown Start` / `Drawdown End`: Start and end datetime for this largest drawdown (can also be visualized via the `plot-dataframe` sub-command).
- `Market change`: Change of the market during the backtest period. Calculated as average of all pairs changes from the first to the last candle using the "close" column. - `Market change`: Change of the market during the backtest period. Calculated as average of all pairs changes from the first to the last candle using the "close" column.
### Daily / Weekly / Monthly breakdown
You can get an overview over daily / weekly or monthly results by using the `--breakdown <>` switch.
To visualize daily and weekly breakdowns, you can use the following:
``` bash
freqtrade backtesting --strategy MyAwesomeStrategy --breakdown day month
```
``` output
======================== DAY BREAKDOWN =========================
| Day | Tot Profit USDT | Wins | Draws | Losses |
|------------+-------------------+--------+---------+----------|
| 03/07/2021 | 200.0 | 2 | 0 | 0 |
| 04/07/2021 | -50.31 | 0 | 0 | 2 |
| 05/07/2021 | 220.611 | 3 | 2 | 0 |
| 06/07/2021 | 150.974 | 3 | 0 | 2 |
| 07/07/2021 | -70.193 | 1 | 0 | 2 |
| 08/07/2021 | 212.413 | 2 | 0 | 3 |
```
The output will show a table containing the realized absolute Profit (in stake currency) for the given timeperiod, as well as wins, draws and losses that materialized (closed) on this day.
### Further backtest-result analysis ### Further backtest-result analysis
To further analyze your backtest results, you can [export the trades](#exporting-trades-to-file). To further analyze your backtest results, you can [export the trades](#exporting-trades-to-file).

View File

@ -667,6 +667,7 @@ usage: freqtrade hyperopt-show [-h] [-v] [--logfile FILE] [-V] [-c PATH]
[--profitable] [-n INT] [--print-json] [--profitable] [-n INT] [--print-json]
[--hyperopt-filename FILENAME] [--no-header] [--hyperopt-filename FILENAME] [--no-header]
[--disable-param-export] [--disable-param-export]
[--breakdown {day,week,month} [{day,week,month} ...]]
optional arguments: optional arguments:
-h, --help show this help message and exit -h, --help show this help message and exit
@ -680,6 +681,8 @@ optional arguments:
--no-header Do not print epoch details header. --no-header Do not print epoch details header.
--disable-param-export --disable-param-export
Disable automatic hyperopt parameter export. Disable automatic hyperopt parameter export.
--breakdown {day,week,month} [{day,week,month} ...]
Show backtesting breakdown per [day, week, month].
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

@ -23,7 +23,8 @@ ARGS_COMMON_OPTIMIZE = ["timeframe", "timerange", "dataformat_ohlcv",
ARGS_BACKTEST = ARGS_COMMON_OPTIMIZE + ["position_stacking", "use_max_market_positions", ARGS_BACKTEST = ARGS_COMMON_OPTIMIZE + ["position_stacking", "use_max_market_positions",
"enable_protections", "dry_run_wallet", "timeframe_detail", "enable_protections", "dry_run_wallet", "timeframe_detail",
"strategy_list", "export", "exportfilename"] "strategy_list", "export", "exportfilename",
"backtest_breakdown"]
ARGS_HYPEROPT = ARGS_COMMON_OPTIMIZE + ["hyperopt", "hyperopt_path", ARGS_HYPEROPT = ARGS_COMMON_OPTIMIZE + ["hyperopt", "hyperopt_path",
"position_stacking", "use_max_market_positions", "position_stacking", "use_max_market_positions",
@ -89,7 +90,7 @@ ARGS_HYPEROPT_LIST = ["hyperopt_list_best", "hyperopt_list_profitable",
ARGS_HYPEROPT_SHOW = ["hyperopt_list_best", "hyperopt_list_profitable", "hyperopt_show_index", ARGS_HYPEROPT_SHOW = ["hyperopt_list_best", "hyperopt_list_profitable", "hyperopt_show_index",
"print_json", "hyperoptexportfilename", "hyperopt_show_no_header", "print_json", "hyperoptexportfilename", "hyperopt_show_no_header",
"disableparamexport"] "disableparamexport", "backtest_breakdown"]
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-data", "list-markets", "list-pairs", "list-strategies", "list-data",

View File

@ -193,6 +193,12 @@ AVAILABLE_CLI_OPTIONS = {
type=float, type=float,
metavar='FLOAT', metavar='FLOAT',
), ),
"backtest_breakdown": Arg(
'--breakdown',
help='Show backtesting breakdown per [day, week, month].',
nargs='+',
choices=constants.BACKTEST_BREAKDOWNS
),
# Edge # Edge
"stoploss_range": Arg( "stoploss_range": Arg(
'--stoplosses', '--stoplosses',

View File

@ -96,7 +96,7 @@ def start_hyperopt_show(args: Dict[str, Any]) -> None:
if 'strategy_name' in metrics: if 'strategy_name' in metrics:
strategy_name = metrics['strategy_name'] strategy_name = metrics['strategy_name']
show_backtest_result(strategy_name, metrics, show_backtest_result(strategy_name, metrics,
metrics['stake_currency']) metrics['stake_currency'], config.get('backtest_breakdown', []))
HyperoptTools.try_export_params(config, strategy_name, val) HyperoptTools.try_export_params(config, strategy_name, val)

View File

@ -269,8 +269,12 @@ class Configuration:
self._args_to_config(config, argname='export', self._args_to_config(config, argname='export',
logstring='Parameter --export detected: {} ...') logstring='Parameter --export detected: {} ...')
self._args_to_config(config, argname='backtest_breakdown',
logstring='Parameter --breakdown detected ...')
self._args_to_config(config, argname='disableparamexport', self._args_to_config(config, argname='disableparamexport',
logstring='Parameter --disableparamexport detected: {} ...') logstring='Parameter --disableparamexport detected: {} ...')
# Edge section: # Edge section:
if 'stoploss_range' in self.args and self.args["stoploss_range"]: if 'stoploss_range' in self.args and self.args["stoploss_range"]:
txt_range = eval(self.args["stoploss_range"]) txt_range = eval(self.args["stoploss_range"])

View File

@ -32,6 +32,7 @@ AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList',
'ShuffleFilter', 'SpreadFilter', 'VolatilityFilter'] 'ShuffleFilter', 'SpreadFilter', 'VolatilityFilter']
AVAILABLE_PROTECTIONS = ['CooldownPeriod', 'LowProfitPairs', 'MaxDrawdown', 'StoplossGuard'] AVAILABLE_PROTECTIONS = ['CooldownPeriod', 'LowProfitPairs', 'MaxDrawdown', 'StoplossGuard']
AVAILABLE_DATAHANDLERS = ['json', 'jsongz', 'hdf5'] AVAILABLE_DATAHANDLERS = ['json', 'jsongz', 'hdf5']
BACKTEST_BREAKDOWNS = ['day', 'week', 'month']
DRY_RUN_WALLET = 1000 DRY_RUN_WALLET = 1000
DATETIME_PRINT_FORMAT = '%Y-%m-%d %H:%M:%S' DATETIME_PRINT_FORMAT = '%Y-%m-%d %H:%M:%S'
MATH_CLOSE_PREC = 1e-14 # Precision used for float comparisons MATH_CLOSE_PREC = 1e-14 # Precision used for float comparisons
@ -150,6 +151,10 @@ CONF_SCHEMA = {
'ignore_buying_expired_candle_after': {'type': 'number'}, 'ignore_buying_expired_candle_after': {'type': 'number'},
'trading_mode': {'type': 'string', 'enum': TRADING_MODES}, 'trading_mode': {'type': 'string', 'enum': TRADING_MODES},
'collateral_type': {'type': 'string', 'enum': COLLATERAL_TYPES}, 'collateral_type': {'type': 'string', 'enum': COLLATERAL_TYPES},
'backtest_breakdown': {
'type': 'array',
'items': {'type': 'string', 'enum': BACKTEST_BREAKDOWNS}
},
'bot_name': {'type': 'string'}, 'bot_name': {'type': 'string'},
'unfilledtimeout': { 'unfilledtimeout': {
'type': 'object', 'type': 'object',

View File

@ -90,7 +90,6 @@ class Exchange:
self._api: ccxt.Exchange = None self._api: ccxt.Exchange = None
self._api_async: ccxt_async.Exchange = None self._api_async: ccxt_async.Exchange = None
self._markets: Dict = {} self._markets: Dict = {}
self._leverage_brackets: Dict = {}
self._config.update(config) self._config.update(config)
@ -159,9 +158,6 @@ class Exchange:
self._api_async = self._init_ccxt( self._api_async = self._init_ccxt(
exchange_config, ccxt_async, ccxt_kwargs=ccxt_async_config) exchange_config, ccxt_async, ccxt_kwargs=ccxt_async_config)
if self.trading_mode != TradingMode.SPOT:
self.fill_leverage_brackets()
logger.info('Using Exchange "%s"', self.name) logger.info('Using Exchange "%s"', self.name)
if validate: if validate:
@ -184,6 +180,10 @@ class Exchange:
self.markets_refresh_interval: int = exchange_config.get( self.markets_refresh_interval: int = exchange_config.get(
"markets_refresh_interval", 60) * 60 "markets_refresh_interval", 60) * 60
self._leverage_brackets: Dict = {}
if self.trading_mode != TradingMode.SPOT:
self.fill_leverage_brackets()
def __del__(self): def __del__(self):
""" """
Destructor - clean up async stuff Destructor - clean up async stuff
@ -1707,6 +1707,7 @@ class Exchange:
""" """
Assigns property _leverage_brackets to a dictionary of information about the leverage Assigns property _leverage_brackets to a dictionary of information about the leverage
allowed on each pair allowed on each pair
Not used if the exchange has a static max leverage value for the account or each pair
""" """
return return
@ -1716,6 +1717,14 @@ class Exchange:
:param pair: The base/quote currency pair being traded :param pair: The base/quote currency pair being traded
:nominal_value: The total value of the trade in quote currency (collateral + debt) :nominal_value: The total value of the trade in quote currency (collateral + debt)
""" """
market = self.markets[pair]
if (
'limits' in market and
'leverage' in market['limits'] and
'max' in market['limits']['leverage']
):
return market['limits']['leverage']['max']
else:
return 1.0 return 1.0
@retrier @retrier

View File

@ -169,21 +169,6 @@ class Ftx(Exchange):
return safe_value_fallback2(order, order, 'id_stop', 'id') return safe_value_fallback2(order, order, 'id_stop', 'id')
return order['id'] return order['id']
def fill_leverage_brackets(self):
"""
FTX leverage is static across the account, and doesn't change from pair to pair,
so _leverage_brackets doesn't need to be set
"""
return
def get_max_leverage(self, pair: Optional[str], nominal_value: Optional[float]) -> float:
"""
Returns the maximum leverage that a pair can be traded at, which is always 20 on ftx
:param pair: Here for super method, not used on FTX
:nominal_value: Here for super method, not used on FTX
"""
return 20.0
def _get_mark_price_history( def _get_mark_price_history(
self, self,
pair: str, pair: str,
@ -191,7 +176,7 @@ class Ftx(Exchange):
end: Optional[int] end: Optional[int]
) -> Dict: ) -> Dict:
""" """
Get's the mark price history for a pair Get's the index price history for a pair
""" """
if end: if end:
params = { params = {

View File

@ -139,40 +139,6 @@ class Kraken(Exchange):
except ccxt.BaseError as e: except ccxt.BaseError as e:
raise OperationalException(e) from e raise OperationalException(e) from e
def fill_leverage_brackets(self):
"""
Assigns property _leverage_brackets to a dictionary of information about the leverage
allowed on each pair
"""
leverages = {}
for pair, market in self.markets.items():
leverages[pair] = [1]
info = market['info']
leverage_buy = info.get('leverage_buy', [])
leverage_sell = info.get('leverage_sell', [])
if len(leverage_buy) > 0 or len(leverage_sell) > 0:
if leverage_buy != leverage_sell:
logger.warning(
f"The buy({leverage_buy}) and sell({leverage_sell}) leverage are not equal"
"for {pair}. Please notify freqtrade because this has never happened before"
)
if max(leverage_buy) <= max(leverage_sell):
leverages[pair] += [int(lev) for lev in leverage_buy]
else:
leverages[pair] += [int(lev) for lev in leverage_sell]
else:
leverages[pair] += [int(lev) for lev in leverage_buy]
self._leverage_brackets = leverages
def get_max_leverage(self, pair: Optional[str], nominal_value: Optional[float]) -> float:
"""
Returns the maximum leverage that a pair can be traded at
:param pair: The base/quote currency pair being traded
:nominal_value: Here for super class, not needed on Kraken
"""
return float(max(self._leverage_brackets[pair]))
def _set_leverage( def _set_leverage(
self, self,
leverage: float, leverage: float,

View File

@ -4,7 +4,7 @@ from pathlib import Path
from typing import Any, Dict, List, Union from typing import Any, Dict, List, Union
from numpy import int64 from numpy import int64
from pandas import DataFrame from pandas import DataFrame, to_datetime
from tabulate import tabulate from tabulate import tabulate
from freqtrade.constants import DATETIME_PRINT_FORMAT, LAST_BT_RESULT_FN, UNLIMITED_STAKE_AMOUNT from freqtrade.constants import DATETIME_PRINT_FORMAT, LAST_BT_RESULT_FN, UNLIMITED_STAKE_AMOUNT
@ -189,7 +189,6 @@ def generate_strategy_comparison(all_results: Dict) -> List[Dict]:
def generate_edge_table(results: dict) -> str: def generate_edge_table(results: dict) -> str:
floatfmt = ('s', '.10g', '.2f', '.2f', '.2f', '.2f', 'd', 'd', 'd') floatfmt = ('s', '.10g', '.2f', '.2f', '.2f', '.2f', 'd', 'd', 'd')
tabular_data = [] tabular_data = []
headers = ['Pair', 'Stoploss', 'Win Rate', 'Risk Reward Ratio', headers = ['Pair', 'Stoploss', 'Win Rate', 'Risk Reward Ratio',
@ -214,6 +213,41 @@ def generate_edge_table(results: dict) -> str:
floatfmt=floatfmt, tablefmt="orgtbl", stralign="right") # type: ignore floatfmt=floatfmt, tablefmt="orgtbl", stralign="right") # type: ignore
def _get_resample_from_period(period: str) -> str:
if period == 'day':
return '1d'
if period == 'week':
return '1w'
if period == 'month':
return '1M'
raise ValueError(f"Period {period} is not supported.")
def generate_periodic_breakdown_stats(trade_list: List, period: str) -> List[Dict[str, Any]]:
results = DataFrame.from_records(trade_list)
if len(results) == 0:
return []
results['close_date'] = to_datetime(results['close_date'], utc=True)
resample_period = _get_resample_from_period(period)
resampled = results.resample(resample_period, on='close_date')
stats = []
for name, day in resampled:
profit_abs = day['profit_abs'].sum().round(10)
wins = sum(day['profit_abs'] > 0)
draws = sum(day['profit_abs'] == 0)
loses = sum(day['profit_abs'] < 0)
stats.append(
{
'date': name.strftime('%d/%m/%Y'),
'profit_abs': profit_abs,
'wins': wins,
'draws': draws,
'loses': loses
}
)
return stats
def generate_trading_stats(results: DataFrame) -> Dict[str, Any]: def generate_trading_stats(results: DataFrame) -> Dict[str, Any]:
""" Generate overall trade statistics """ """ Generate overall trade statistics """
if len(results) == 0: if len(results) == 0:
@ -329,7 +363,7 @@ def generate_strategy_stats(btdata: Dict[str, DataFrame],
results['open_timestamp'] = results['open_date'].view(int64) // 1e6 results['open_timestamp'] = results['open_date'].view(int64) // 1e6
results['close_timestamp'] = results['close_date'].view(int64) // 1e6 results['close_timestamp'] = results['close_date'].view(int64) // 1e6
backtest_days = (max_date - min_date).days backtest_days = (max_date - min_date).days or 1
strat_stats = { strat_stats = {
'trades': results.to_dict(orient='records'), 'trades': results.to_dict(orient='records'),
'locks': [lock.to_json() for lock in content['locks']], 'locks': [lock.to_json() for lock in content['locks']],
@ -338,6 +372,8 @@ def generate_strategy_stats(btdata: Dict[str, DataFrame],
'results_per_pair': pair_results, 'results_per_pair': pair_results,
'sell_reason_summary': sell_reason_stats, 'sell_reason_summary': sell_reason_stats,
'left_open_trades': left_open_results, 'left_open_trades': left_open_results,
# 'days_breakdown_stats': days_breakdown_stats,
'total_trades': len(results), 'total_trades': len(results),
'total_volume': float(results['stake_amount'].sum()), 'total_volume': float(results['stake_amount'].sum()),
'avg_stake_amount': results['stake_amount'].mean() if len(results) > 0 else 0, 'avg_stake_amount': results['stake_amount'].mean() if len(results) > 0 else 0,
@ -354,7 +390,7 @@ def generate_strategy_stats(btdata: Dict[str, DataFrame],
'backtest_run_start_ts': content['backtest_start_time'], 'backtest_run_start_ts': content['backtest_start_time'],
'backtest_run_end_ts': content['backtest_end_time'], 'backtest_run_end_ts': content['backtest_end_time'],
'trades_per_day': round(len(results) / backtest_days, 2) if backtest_days > 0 else 0, 'trades_per_day': round(len(results) / backtest_days, 2),
'market_change': market_change, 'market_change': market_change,
'pairlist': list(btdata.keys()), 'pairlist': list(btdata.keys()),
'stake_amount': config['stake_amount'], 'stake_amount': config['stake_amount'],
@ -506,6 +542,28 @@ def text_table_sell_reason(sell_reason_stats: List[Dict[str, Any]], stake_curren
return tabulate(output, headers=headers, tablefmt="orgtbl", stralign="right") return tabulate(output, headers=headers, tablefmt="orgtbl", stralign="right")
def text_table_periodic_breakdown(days_breakdown_stats: List[Dict[str, Any]],
stake_currency: str, period: str) -> str:
"""
Generate small table with Backtest results by days
:param days_breakdown_stats: Days breakdown metrics
:param stake_currency: Stakecurrency used
:return: pretty printed table with tabulate as string
"""
headers = [
period.capitalize(),
f'Tot Profit {stake_currency}',
'Wins',
'Draws',
'Losses',
]
output = [[
d['date'], round_coin_value(d['profit_abs'], stake_currency, False),
d['wins'], d['draws'], d['loses'],
] for d in days_breakdown_stats]
return tabulate(output, headers=headers, tablefmt="orgtbl", stralign="right")
def text_table_strategy(strategy_results, stake_currency: str) -> str: def text_table_strategy(strategy_results, stake_currency: str) -> str:
""" """
Generate summary table per strategy Generate summary table per strategy
@ -557,7 +615,10 @@ def text_table_add_metrics(strat_results: Dict) -> str:
strat_results['stake_currency'])), strat_results['stake_currency'])),
('Absolute profit ', round_coin_value(strat_results['profit_total_abs'], ('Absolute profit ', round_coin_value(strat_results['profit_total_abs'],
strat_results['stake_currency'])), strat_results['stake_currency'])),
('Total profit %', f"{round(strat_results['profit_total'] * 100, 2):}%"), ('Total profit %', f"{round(strat_results['profit_total'] * 100, 2)}%"),
('Trades per day', strat_results['trades_per_day']),
('Avg. daily profit %',
f"{round(strat_results['profit_total'] / strat_results['backtest_days'] * 100, 2)}%"),
('Avg. stake amount', round_coin_value(strat_results['avg_stake_amount'], ('Avg. stake amount', round_coin_value(strat_results['avg_stake_amount'],
strat_results['stake_currency'])), strat_results['stake_currency'])),
('Total trade volume', round_coin_value(strat_results['total_volume'], ('Total trade volume', round_coin_value(strat_results['total_volume'],
@ -614,7 +675,8 @@ def text_table_add_metrics(strat_results: Dict) -> str:
return message return message
def show_backtest_result(strategy: str, results: Dict[str, Any], stake_currency: str): def show_backtest_result(strategy: str, results: Dict[str, Any], stake_currency: str,
backtest_breakdown=[]):
""" """
Print results for one strategy Print results for one strategy
""" """
@ -636,6 +698,15 @@ def show_backtest_result(strategy: str, results: Dict[str, Any], stake_currency:
print(' LEFT OPEN TRADES REPORT '.center(len(table.splitlines()[0]), '=')) print(' LEFT OPEN TRADES REPORT '.center(len(table.splitlines()[0]), '='))
print(table) print(table)
for period in backtest_breakdown:
days_breakdown_stats = generate_periodic_breakdown_stats(
trade_list=results['trades'], period=period)
table = text_table_periodic_breakdown(days_breakdown_stats=days_breakdown_stats,
stake_currency=stake_currency, period=period)
if isinstance(table, str) and len(table) > 0:
print(f' {period.upper()} BREAKDOWN '.center(len(table.splitlines()[0]), '='))
print(table)
table = text_table_add_metrics(results) table = text_table_add_metrics(results)
if isinstance(table, str) and len(table) > 0: if isinstance(table, str) and len(table) > 0:
print(' SUMMARY METRICS '.center(len(table.splitlines()[0]), '=')) print(' SUMMARY METRICS '.center(len(table.splitlines()[0]), '='))
@ -650,7 +721,9 @@ def show_backtest_results(config: Dict, backtest_stats: Dict):
stake_currency = config['stake_currency'] stake_currency = config['stake_currency']
for strategy, results in backtest_stats['strategy'].items(): for strategy, results in backtest_stats['strategy'].items():
show_backtest_result(strategy, results, stake_currency) show_backtest_result(
strategy, results, stake_currency,
config.get('backtest_breakdown', []))
if len(backtest_stats['strategy']) > 1: if len(backtest_stats['strategy']) > 1:
# Print Strategy summary table # Print Strategy summary table

View File

@ -590,10 +590,10 @@ def get_markets():
'min': 0.0001, 'min': 0.0001,
'max': 500000, 'max': 500000,
}, },
}, 'leverage': {
'info': { 'min': 1.0,
'leverage_buy': ['2'], 'max': 2.0
'leverage_sell': ['2'], }
}, },
}, },
'TKN/BTC': { 'TKN/BTC': {
@ -619,10 +619,10 @@ def get_markets():
'min': 0.0001, 'min': 0.0001,
'max': 500000, 'max': 500000,
}, },
}, 'leverage': {
'info': { 'min': 1.0,
'leverage_buy': ['2', '3', '4', '5'], 'max': 5.0
'leverage_sell': ['2', '3', '4', '5'], }
}, },
}, },
'BLK/BTC': { 'BLK/BTC': {
@ -647,10 +647,10 @@ def get_markets():
'min': 0.0001, 'min': 0.0001,
'max': 500000, 'max': 500000,
}, },
'leverage': {
'min': 1.0,
'max': 3.0
}, },
'info': {
'leverage_buy': ['2', '3'],
'leverage_sell': ['2', '3'],
}, },
}, },
'LTC/BTC': { 'LTC/BTC': {
@ -676,10 +676,7 @@ def get_markets():
'max': 500000, 'max': 500000,
}, },
}, },
'info': { 'info': {},
'leverage_buy': [],
'leverage_sell': [],
},
}, },
'XRP/BTC': { 'XRP/BTC': {
'id': 'xrpbtc', 'id': 'xrpbtc',
@ -757,10 +754,7 @@ def get_markets():
'max': None 'max': None
} }
}, },
'info': { 'info': {},
'leverage_buy': [],
'leverage_sell': [],
},
}, },
'ETH/USDT': { 'ETH/USDT': {
'id': 'USDT-ETH', 'id': 'USDT-ETH',

View File

@ -3275,6 +3275,19 @@ def test__ccxt_config(
assert exchange._ccxt_config == ccxt_config assert exchange._ccxt_config == ccxt_config
@pytest.mark.parametrize('pair,nominal_value,max_lev', [
("ETH/BTC", 0.0, 2.0),
("TKN/BTC", 100.0, 5.0),
("BLK/BTC", 173.31, 3.0),
("LTC/BTC", 0.0, 1.0),
("TKN/USDT", 210.30, 1.0),
])
def test_get_max_leverage(default_conf, mocker, pair, nominal_value, max_lev):
# Binance has a different method of getting the max leverage
exchange = get_patched_exchange(mocker, default_conf, id="kraken")
assert exchange.get_max_leverage(pair, nominal_value) == max_lev
def test_get_mark_price(): def test_get_mark_price():
return return

View File

@ -250,20 +250,3 @@ def test_get_order_id(mocker, default_conf):
} }
} }
assert exchange.get_order_id_conditional(order) == '1111' assert exchange.get_order_id_conditional(order) == '1111'
@pytest.mark.parametrize('pair,nominal_value,max_lev', [
("ADA/BTC", 0.0, 20.0),
("BTC/EUR", 100.0, 20.0),
("ZEC/USD", 173.31, 20.0),
])
def test_get_max_leverage_ftx(default_conf, mocker, pair, nominal_value, max_lev):
exchange = get_patched_exchange(mocker, default_conf, id="ftx")
assert exchange.get_max_leverage(pair, nominal_value) == max_lev
def test_fill_leverage_brackets_ftx(default_conf, mocker):
# FTX only has one account wide leverage, so there's no leverage brackets
exchange = get_patched_exchange(mocker, default_conf, id="ftx")
exchange.fill_leverage_brackets()
assert exchange._leverage_brackets == {}

View File

@ -295,42 +295,3 @@ def test_stoploss_adjust_kraken(mocker, default_conf, sl1, sl2, sl3, side):
# Test with invalid order case ... # Test with invalid order case ...
order['type'] = 'stop_loss_limit' order['type'] = 'stop_loss_limit'
assert not exchange.stoploss_adjust(sl3, order, side=side) assert not exchange.stoploss_adjust(sl3, order, side=side)
@pytest.mark.parametrize('pair,nominal_value,max_lev', [
("ADA/BTC", 0.0, 3.0),
("BTC/EUR", 100.0, 5.0),
("ZEC/USD", 173.31, 2.0),
])
def test_get_max_leverage_kraken(default_conf, mocker, pair, nominal_value, max_lev):
exchange = get_patched_exchange(mocker, default_conf, id="kraken")
exchange._leverage_brackets = {
'ADA/BTC': ['2', '3'],
'BTC/EUR': ['2', '3', '4', '5'],
'ZEC/USD': ['2']
}
assert exchange.get_max_leverage(pair, nominal_value) == max_lev
def test_fill_leverage_brackets_kraken(default_conf, mocker):
api_mock = MagicMock()
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kraken")
exchange.fill_leverage_brackets()
assert exchange._leverage_brackets == {
'BLK/BTC': [1, 2, 3],
'TKN/BTC': [1, 2, 3, 4, 5],
'ETH/BTC': [1, 2],
'LTC/BTC': [1],
'XRP/BTC': [1],
'NEO/BTC': [1],
'BTT/BTC': [1],
'ETH/USDT': [1],
'LTC/USDT': [1],
'LTC/USD': [1],
'XLTCUSDT': [1],
'LTC/ETH': [1],
'NEO/USDT': [1],
'TKN/USDT': [1],
'XRP/USDT': [1]
}

View File

@ -1121,6 +1121,7 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat
'--timerange', '1510694220-1510700340', '--timerange', '1510694220-1510700340',
'--enable-position-stacking', '--enable-position-stacking',
'--disable-max-market-positions', '--disable-max-market-positions',
'--breakdown', 'day',
'--strategy-list', '--strategy-list',
CURRENT_TEST_STRATEGY, CURRENT_TEST_STRATEGY,
'TestStrategyLegacyV1', 'TestStrategyLegacyV1',
@ -1149,6 +1150,7 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat
captured = capsys.readouterr() captured = capsys.readouterr()
assert 'BACKTESTING REPORT' in captured.out assert 'BACKTESTING REPORT' in captured.out
assert 'SELL REASON STATS' in captured.out assert 'SELL REASON STATS' in captured.out
assert 'DAY BREAKDOWN' in captured.out
assert 'LEFT OPEN TRADES REPORT' in captured.out assert 'LEFT OPEN TRADES REPORT' in captured.out
assert '2017-11-14 21:17:00 -> 2017-11-14 22:58:00 | Max open trades : 1' in captured.out assert '2017-11-14 21:17:00 -> 2017-11-14 22:58:00 | Max open trades : 1' in captured.out
assert 'STRATEGY SUMMARY' in captured.out assert 'STRATEGY SUMMARY' in captured.out

View File

@ -13,8 +13,10 @@ from freqtrade.data import history
from freqtrade.data.btanalysis import get_latest_backtest_filename, load_backtest_data from freqtrade.data.btanalysis import get_latest_backtest_filename, load_backtest_data
from freqtrade.edge import PairInfo from freqtrade.edge import PairInfo
from freqtrade.enums import SellType from freqtrade.enums import SellType
from freqtrade.optimize.optimize_reports import (generate_backtest_stats, generate_daily_stats, from freqtrade.optimize.optimize_reports import (_get_resample_from_period, generate_backtest_stats,
generate_edge_table, generate_pair_metrics, generate_daily_stats, generate_edge_table,
generate_pair_metrics,
generate_periodic_breakdown_stats,
generate_sell_reason_stats, generate_sell_reason_stats,
generate_strategy_comparison, generate_strategy_comparison,
generate_trading_stats, store_backtest_stats, generate_trading_stats, store_backtest_stats,
@ -378,3 +380,31 @@ def test_generate_edge_table():
assert generate_edge_table(results).count('| ETH/BTC |') == 1 assert generate_edge_table(results).count('| ETH/BTC |') == 1
assert generate_edge_table(results).count( assert generate_edge_table(results).count(
'| Risk Reward Ratio | Required Risk Reward | Expectancy |') == 1 '| Risk Reward Ratio | Required Risk Reward | Expectancy |') == 1
def test_generate_periodic_breakdown_stats(testdatadir):
filename = testdatadir / "backtest-result_new.json"
bt_data = load_backtest_data(filename).to_dict(orient='records')
res = generate_periodic_breakdown_stats(bt_data, 'day')
assert isinstance(res, list)
assert len(res) == 21
day = res[0]
assert 'date' in day
assert 'draws' in day
assert 'loses' in day
assert 'wins' in day
assert 'profit_abs' in day
# Select empty dataframe!
res = generate_periodic_breakdown_stats([], 'day')
assert res == []
def test__get_resample_from_period():
assert _get_resample_from_period('day') == '1d'
assert _get_resample_from_period('week') == '1w'
assert _get_resample_from_period('month') == '1M'
with pytest.raises(ValueError, match=r"Period noooo is not supported."):
_get_resample_from_period('noooo')