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,7 +1717,15 @@ 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)
""" """
return 1.0 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
@retrier @retrier
def _set_leverage( def _set_leverage(

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': {
'info': { 'min': 1.0,
'leverage_buy': ['2', '3'], 'max': 3.0
'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')

View File

@ -1969,7 +1969,7 @@ def test_handle_trade(
assert trade.close_date is not None assert trade.close_date is not None
@ pytest.mark.parametrize("is_short", [False, True]) @pytest.mark.parametrize("is_short", [False, True])
def test_handle_overlapping_signals( def test_handle_overlapping_signals(
default_conf_usdt, ticker_usdt, limit_order_open, fee, mocker, is_short default_conf_usdt, ticker_usdt, limit_order_open, fee, mocker, is_short
) -> None: ) -> None:
@ -2045,7 +2045,7 @@ def test_handle_overlapping_signals(
assert freqtrade.handle_trade(trades[0]) is True assert freqtrade.handle_trade(trades[0]) is True
@ pytest.mark.parametrize("is_short", [False, True]) @pytest.mark.parametrize("is_short", [False, True])
def test_handle_trade_roi(default_conf_usdt, ticker_usdt, limit_order_open, fee, mocker, caplog, def test_handle_trade_roi(default_conf_usdt, ticker_usdt, limit_order_open, fee, mocker, caplog,
is_short) -> None: is_short) -> None:
@ -2087,7 +2087,7 @@ def test_handle_trade_roi(default_conf_usdt, ticker_usdt, limit_order_open, fee,
caplog) caplog)
@ pytest.mark.parametrize("is_short", [False, True]) @pytest.mark.parametrize("is_short", [False, True])
def test_handle_trade_use_sell_signal( def test_handle_trade_use_sell_signal(
default_conf_usdt, ticker_usdt, limit_order_open, fee, mocker, caplog, is_short default_conf_usdt, ticker_usdt, limit_order_open, fee, mocker, caplog, is_short
) -> None: ) -> None:
@ -2129,7 +2129,7 @@ def test_handle_trade_use_sell_signal(
caplog) caplog)
@ pytest.mark.parametrize("is_short", [False, True]) @pytest.mark.parametrize("is_short", [False, True])
def test_close_trade( def test_close_trade(
default_conf_usdt, ticker_usdt, limit_order_open, default_conf_usdt, ticker_usdt, limit_order_open,
limit_order, fee, mocker, is_short limit_order, fee, mocker, is_short
@ -2176,7 +2176,7 @@ def test_bot_loop_start_called_once(mocker, default_conf_usdt, caplog):
assert ftbot.strategy.analyze.call_count == 1 assert ftbot.strategy.analyze.call_count == 1
@ pytest.mark.parametrize("is_short", [False, True]) @pytest.mark.parametrize("is_short", [False, True])
def test_check_handle_timedout_buy_usercustom( def test_check_handle_timedout_buy_usercustom(
default_conf_usdt, ticker_usdt, limit_buy_order_old, open_trade, default_conf_usdt, ticker_usdt, limit_buy_order_old, open_trade,
limit_sell_order_old, fee, mocker, is_short limit_sell_order_old, fee, mocker, is_short
@ -2251,7 +2251,7 @@ def test_check_handle_timedout_buy_usercustom(
assert freqtrade.strategy.check_buy_timeout.call_count == 1 assert freqtrade.strategy.check_buy_timeout.call_count == 1
@ pytest.mark.parametrize("is_short", [False, True]) @pytest.mark.parametrize("is_short", [False, True])
def test_check_handle_timedout_buy( def test_check_handle_timedout_buy(
default_conf_usdt, ticker_usdt, limit_buy_order_old, open_trade, default_conf_usdt, ticker_usdt, limit_buy_order_old, open_trade,
limit_sell_order_old, fee, mocker, is_short limit_sell_order_old, fee, mocker, is_short
@ -2292,7 +2292,7 @@ def test_check_handle_timedout_buy(
assert freqtrade.strategy.check_buy_timeout.call_count == 0 assert freqtrade.strategy.check_buy_timeout.call_count == 0
@ pytest.mark.parametrize("is_short", [False, True]) @pytest.mark.parametrize("is_short", [False, True])
def test_check_handle_cancelled_buy( def test_check_handle_cancelled_buy(
default_conf_usdt, ticker_usdt, limit_buy_order_old, open_trade, default_conf_usdt, ticker_usdt, limit_buy_order_old, open_trade,
limit_sell_order_old, fee, mocker, caplog, is_short limit_sell_order_old, fee, mocker, caplog, is_short
@ -2325,7 +2325,7 @@ def test_check_handle_cancelled_buy(
f"{'Sell' if is_short else 'Buy'} order cancelled on exchange for Trade.*", caplog) f"{'Sell' if is_short else 'Buy'} order cancelled on exchange for Trade.*", caplog)
@ pytest.mark.parametrize("is_short", [False, True]) @pytest.mark.parametrize("is_short", [False, True])
def test_check_handle_timedout_buy_exception( def test_check_handle_timedout_buy_exception(
default_conf_usdt, ticker_usdt, limit_buy_order_old, open_trade, default_conf_usdt, ticker_usdt, limit_buy_order_old, open_trade,
is_short, fee, mocker is_short, fee, mocker
@ -2354,7 +2354,7 @@ def test_check_handle_timedout_buy_exception(
assert nb_trades == 1 assert nb_trades == 1
@ pytest.mark.parametrize("is_short", [False, True]) @pytest.mark.parametrize("is_short", [False, True])
def test_check_handle_timedout_sell_usercustom( def test_check_handle_timedout_sell_usercustom(
default_conf_usdt, ticker_usdt, limit_sell_order_old, mocker, default_conf_usdt, ticker_usdt, limit_sell_order_old, mocker,
is_short, open_trade_usdt is_short, open_trade_usdt
@ -2406,7 +2406,7 @@ def test_check_handle_timedout_sell_usercustom(
assert freqtrade.strategy.check_sell_timeout.call_count == 1 assert freqtrade.strategy.check_sell_timeout.call_count == 1
@ pytest.mark.parametrize("is_short", [False, True]) @pytest.mark.parametrize("is_short", [False, True])
def test_check_handle_timedout_sell( def test_check_handle_timedout_sell(
default_conf_usdt, ticker_usdt, limit_sell_order_old, default_conf_usdt, ticker_usdt, limit_sell_order_old,
mocker, is_short, open_trade_usdt mocker, is_short, open_trade_usdt
@ -2439,7 +2439,7 @@ def test_check_handle_timedout_sell(
assert freqtrade.strategy.check_sell_timeout.call_count == 0 assert freqtrade.strategy.check_sell_timeout.call_count == 0
@ pytest.mark.parametrize("is_short", [False, True]) @pytest.mark.parametrize("is_short", [False, True])
def test_check_handle_cancelled_sell( def test_check_handle_cancelled_sell(
default_conf_usdt, ticker_usdt, limit_sell_order_old, open_trade_usdt, default_conf_usdt, ticker_usdt, limit_sell_order_old, open_trade_usdt,
is_short, mocker, caplog is_short, mocker, caplog
@ -2471,7 +2471,7 @@ def test_check_handle_cancelled_sell(
assert log_has_re("Sell order cancelled on exchange for Trade.*", caplog) assert log_has_re("Sell order cancelled on exchange for Trade.*", caplog)
@ pytest.mark.parametrize("is_short", [False, True]) @pytest.mark.parametrize("is_short", [False, True])
def test_check_handle_timedout_partial( def test_check_handle_timedout_partial(
default_conf_usdt, ticker_usdt, limit_buy_order_old_partial, is_short, default_conf_usdt, ticker_usdt, limit_buy_order_old_partial, is_short,
open_trade, mocker open_trade, mocker
@ -2503,7 +2503,7 @@ def test_check_handle_timedout_partial(
assert trades[0].stake_amount == open_trade.open_rate * trades[0].amount assert trades[0].stake_amount == open_trade.open_rate * trades[0].amount
@ pytest.mark.parametrize("is_short", [False, True]) @pytest.mark.parametrize("is_short", [False, True])
def test_check_handle_timedout_partial_fee( def test_check_handle_timedout_partial_fee(
default_conf_usdt, ticker_usdt, open_trade, caplog, fee, is_short, default_conf_usdt, ticker_usdt, open_trade, caplog, fee, is_short,
limit_buy_order_old_partial, trades_for_order, limit_buy_order_old_partial, trades_for_order,
@ -2545,7 +2545,7 @@ def test_check_handle_timedout_partial_fee(
assert pytest.approx(trades[0].fee_open) == 0.001 assert pytest.approx(trades[0].fee_open) == 0.001
@ pytest.mark.parametrize("is_short", [False, True]) @pytest.mark.parametrize("is_short", [False, True])
def test_check_handle_timedout_partial_except( def test_check_handle_timedout_partial_except(
default_conf_usdt, ticker_usdt, open_trade, caplog, fee, is_short, default_conf_usdt, ticker_usdt, open_trade, caplog, fee, is_short,
limit_buy_order_old_partial, trades_for_order, limit_buy_order_old_partial, trades_for_order,
@ -2619,7 +2619,7 @@ def test_check_handle_timedout_exception(default_conf_usdt, ticker_usdt, open_tr
caplog) caplog)
@ pytest.mark.parametrize("is_short", [False, True]) @pytest.mark.parametrize("is_short", [False, True])
def test_handle_cancel_enter(mocker, caplog, default_conf_usdt, limit_order, def test_handle_cancel_enter(mocker, caplog, default_conf_usdt, limit_order,
is_short) -> None: is_short) -> None:
patch_RPCManager(mocker) patch_RPCManager(mocker)
@ -2667,9 +2667,9 @@ def test_handle_cancel_enter(mocker, caplog, default_conf_usdt, limit_order,
assert log_has_re(r"Order .* for .* not cancelled.", caplog) assert log_has_re(r"Order .* for .* not cancelled.", caplog)
@ pytest.mark.parametrize("is_short", [False, True]) @pytest.mark.parametrize("is_short", [False, True])
@ pytest.mark.parametrize("limit_buy_order_canceled_empty", ['binance', 'ftx', 'kraken', 'bittrex'], @pytest.mark.parametrize("limit_buy_order_canceled_empty", ['binance', 'ftx', 'kraken', 'bittrex'],
indirect=['limit_buy_order_canceled_empty']) indirect=['limit_buy_order_canceled_empty'])
def test_handle_cancel_enter_exchanges(mocker, caplog, default_conf_usdt, is_short, def test_handle_cancel_enter_exchanges(mocker, caplog, default_conf_usdt, is_short,
limit_buy_order_canceled_empty) -> None: limit_buy_order_canceled_empty) -> None:
patch_RPCManager(mocker) patch_RPCManager(mocker)
@ -2690,8 +2690,8 @@ def test_handle_cancel_enter_exchanges(mocker, caplog, default_conf_usdt, is_sho
assert nofiy_mock.call_count == 1 assert nofiy_mock.call_count == 1
@ pytest.mark.parametrize("is_short", [False, True]) @pytest.mark.parametrize("is_short", [False, True])
@ pytest.mark.parametrize('cancelorder', [ @pytest.mark.parametrize('cancelorder', [
{}, {},
{'remaining': None}, {'remaining': None},
'String Return value', 'String Return value',
@ -2789,7 +2789,7 @@ def test_handle_cancel_exit_cancel_exception(mocker, default_conf_usdt) -> None:
assert freqtrade.handle_cancel_exit(trade, order, reason) == 'error cancelling order' assert freqtrade.handle_cancel_exit(trade, order, reason) == 'error cancelling order'
@ pytest.mark.parametrize("is_short, open_rate, amt", [ @pytest.mark.parametrize("is_short, open_rate, amt", [
(False, 2.0, 30.0), (False, 2.0, 30.0),
(True, 2.02, 29.70297029), (True, 2.02, 29.70297029),
]) ])
@ -2864,7 +2864,7 @@ def test_execute_trade_exit_up(default_conf_usdt, ticker_usdt, fee, ticker_usdt_
} == last_msg } == last_msg
@ pytest.mark.parametrize("is_short", [False, True]) @pytest.mark.parametrize("is_short", [False, True])
def test_execute_trade_exit_down(default_conf_usdt, ticker_usdt, fee, ticker_usdt_sell_down, def test_execute_trade_exit_down(default_conf_usdt, ticker_usdt, fee, ticker_usdt_sell_down,
ticker_usdt_sell_up, mocker, is_short) -> None: ticker_usdt_sell_up, mocker, is_short) -> None:
rpc_mock = patch_RPCManager(mocker) rpc_mock = patch_RPCManager(mocker)
@ -2993,7 +2993,7 @@ def test_execute_trade_exit_custom_exit_price(
} == last_msg } == last_msg
@ pytest.mark.parametrize("is_short", [False, True]) @pytest.mark.parametrize("is_short", [False, True])
def test_execute_trade_exit_down_stoploss_on_exchange_dry_run( def test_execute_trade_exit_down_stoploss_on_exchange_dry_run(
default_conf_usdt, ticker_usdt, fee, is_short, ticker_usdt_sell_down, default_conf_usdt, ticker_usdt, fee, is_short, ticker_usdt_sell_down,
ticker_usdt_sell_up, mocker) -> None: ticker_usdt_sell_up, mocker) -> None:
@ -3091,7 +3091,7 @@ def test_execute_trade_exit_sloe_cancel_exception(
assert log_has('Could not cancel stoploss order abcd', caplog) assert log_has('Could not cancel stoploss order abcd', caplog)
@ pytest.mark.parametrize("is_short", [False, True]) @pytest.mark.parametrize("is_short", [False, True])
def test_execute_trade_exit_with_stoploss_on_exchange( def test_execute_trade_exit_with_stoploss_on_exchange(
default_conf_usdt, ticker_usdt, fee, ticker_usdt_sell_up, is_short, mocker) -> None: default_conf_usdt, ticker_usdt, fee, ticker_usdt_sell_up, is_short, mocker) -> None:
@ -3309,7 +3309,7 @@ def test_execute_trade_exit_market_order(
} == last_msg } == last_msg
@ pytest.mark.parametrize("is_short", [False, True]) @pytest.mark.parametrize("is_short", [False, True])
def test_execute_trade_exit_insufficient_funds_error(default_conf_usdt, ticker_usdt, fee, is_short, def test_execute_trade_exit_insufficient_funds_error(default_conf_usdt, ticker_usdt, fee, is_short,
ticker_usdt_sell_up, mocker) -> None: ticker_usdt_sell_up, mocker) -> None:
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
@ -3345,7 +3345,7 @@ def test_execute_trade_exit_insufficient_funds_error(default_conf_usdt, ticker_u
assert mock_insuf.call_count == 1 assert mock_insuf.call_count == 1
@ pytest.mark.parametrize('profit_only,bid,ask,handle_first,handle_second,sell_type,is_short', [ @pytest.mark.parametrize('profit_only,bid,ask,handle_first,handle_second,sell_type,is_short', [
# Enable profit # Enable profit
(True, 2.18, 2.2, False, True, SellType.SELL_SIGNAL.value, False), (True, 2.18, 2.2, False, True, SellType.SELL_SIGNAL.value, False),
(True, 2.18, 2.2, False, True, SellType.SELL_SIGNAL.value, True), (True, 2.18, 2.2, False, True, SellType.SELL_SIGNAL.value, True),
@ -3439,7 +3439,7 @@ def test_sell_not_enough_balance(default_conf_usdt, limit_order, limit_order_ope
assert trade.amount != amnt assert trade.amount != amnt
@ pytest.mark.parametrize('amount_wallet,has_err', [ @pytest.mark.parametrize('amount_wallet,has_err', [
(95.29, False), (95.29, False),
(91.29, True) (91.29, True)
]) ])
@ -3476,7 +3476,7 @@ def test__safe_exit_amount(default_conf_usdt, fee, caplog, mocker, amount_wallet
assert wallet_update.call_count == 1 assert wallet_update.call_count == 1
@ pytest.mark.parametrize("is_short", [False, True]) @pytest.mark.parametrize("is_short", [False, True])
def test_locked_pairs(default_conf_usdt, ticker_usdt, fee, def test_locked_pairs(default_conf_usdt, ticker_usdt, fee,
ticker_usdt_sell_down, mocker, caplog, is_short) -> None: ticker_usdt_sell_down, mocker, caplog, is_short) -> None:
patch_RPCManager(mocker) patch_RPCManager(mocker)
@ -3515,7 +3515,7 @@ def test_locked_pairs(default_conf_usdt, ticker_usdt, fee,
assert log_has_re(f"Pair {trade.pair} is still locked.*", caplog) assert log_has_re(f"Pair {trade.pair} is still locked.*", caplog)
@ pytest.mark.parametrize("is_short", [False, True]) @pytest.mark.parametrize("is_short", [False, True])
def test_ignore_roi_if_buy_signal(default_conf_usdt, limit_order, limit_order_open, is_short, def test_ignore_roi_if_buy_signal(default_conf_usdt, limit_order, limit_order_open, is_short,
fee, mocker) -> None: fee, mocker) -> None:
patch_RPCManager(mocker) patch_RPCManager(mocker)
@ -3561,7 +3561,7 @@ def test_ignore_roi_if_buy_signal(default_conf_usdt, limit_order, limit_order_op
assert trade.sell_reason == SellType.ROI.value assert trade.sell_reason == SellType.ROI.value
@ pytest.mark.parametrize("is_short,val1,val2", [ @pytest.mark.parametrize("is_short,val1,val2", [
(False, 1.5, 1.1), (False, 1.5, 1.1),
(True, 0.5, 0.9) (True, 0.5, 0.9)
]) ])
@ -3623,7 +3623,7 @@ def test_trailing_stop_loss(default_conf_usdt, limit_order_open,
assert trade.sell_reason == SellType.TRAILING_STOP_LOSS.value assert trade.sell_reason == SellType.TRAILING_STOP_LOSS.value
@ pytest.mark.parametrize('offset,trail_if_reached,second_sl,is_short', [ @pytest.mark.parametrize('offset,trail_if_reached,second_sl,is_short', [
(0, False, 2.0394, False), (0, False, 2.0394, False),
(0.011, False, 2.0394, False), (0.011, False, 2.0394, False),
(0.055, True, 1.8, False), (0.055, True, 1.8, False),
@ -3845,7 +3845,7 @@ def test_get_real_amount_no_trade(default_conf_usdt, buy_order_fee, caplog, mock
) )
@ pytest.mark.parametrize( @pytest.mark.parametrize(
'fee_par,fee_reduction_amount,use_ticker_usdt_rate,expected_log', [ 'fee_par,fee_reduction_amount,use_ticker_usdt_rate,expected_log', [
# basic, amount does not change # basic, amount does not change
({'cost': 0.008, 'currency': 'ETH'}, 0, False, None), ({'cost': 0.008, 'currency': 'ETH'}, 0, False, None),
@ -3898,7 +3898,7 @@ def test_get_real_amount(
assert log_has(expected_log, caplog) assert log_has(expected_log, caplog)
@ pytest.mark.parametrize( @pytest.mark.parametrize(
'fee_cost, fee_currency, fee_reduction_amount, expected_fee, expected_log_amount', [ 'fee_cost, fee_currency, fee_reduction_amount, expected_fee, expected_log_amount', [
# basic, amount is reduced by fee # basic, amount is reduced by fee
(None, None, 0.001, 0.001, 7.992), (None, None, 0.001, 0.001, 7.992),
@ -4050,7 +4050,7 @@ def test_get_real_amount_open_trade_usdt(default_conf_usdt, fee, mocker):
assert freqtrade.get_real_amount(trade, order) == amount assert freqtrade.get_real_amount(trade, order) == amount
@ pytest.mark.parametrize('amount,fee_abs,wallet,amount_exp', [ @pytest.mark.parametrize('amount,fee_abs,wallet,amount_exp', [
(8.0, 0.0, 10, 8), (8.0, 0.0, 10, 8),
(8.0, 0.0, 0, 8), (8.0, 0.0, 0, 8),
(8.0, 0.1, 0, 7.9), (8.0, 0.1, 0, 7.9),
@ -4079,11 +4079,11 @@ def test_apply_fee_conditional(default_conf_usdt, fee, mocker,
assert walletmock.call_count == 1 assert walletmock.call_count == 1
@ pytest.mark.parametrize("delta, is_high_delta", [ @pytest.mark.parametrize("delta, is_high_delta", [
(0.1, False), (0.1, False),
(100, True), (100, True),
]) ])
@ pytest.mark.parametrize('is_short, open_rate', [ @pytest.mark.parametrize('is_short, open_rate', [
(False, 2.0), (False, 2.0),
(True, 2.02), (True, 2.02),
]) ])
@ -4129,7 +4129,7 @@ def test_order_book_depth_of_market(
assert whitelist == default_conf_usdt['exchange']['pair_whitelist'] assert whitelist == default_conf_usdt['exchange']['pair_whitelist']
@ pytest.mark.parametrize('exception_thrown,ask,last,order_book_top,order_book', [ @pytest.mark.parametrize('exception_thrown,ask,last,order_book_top,order_book', [
(False, 0.045, 0.046, 2, None), (False, 0.045, 0.046, 2, None),
(True, 0.042, 0.046, 1, {'bids': [[]], 'asks': [[]]}) (True, 0.042, 0.046, 1, {'bids': [[]], 'asks': [[]]})
]) ])
@ -4182,7 +4182,7 @@ def test_check_depth_of_market(default_conf_usdt, mocker, order_book_l2) -> None
assert freqtrade._check_depth_of_market('ETH/BTC', conf, side=SignalDirection.LONG) is False assert freqtrade._check_depth_of_market('ETH/BTC', conf, side=SignalDirection.LONG) is False
@ pytest.mark.parametrize('is_short', [False, True]) @pytest.mark.parametrize('is_short', [False, True])
def test_order_book_ask_strategy( def test_order_book_ask_strategy(
default_conf_usdt, limit_buy_order_usdt_open, limit_buy_order_usdt, fee, is_short, default_conf_usdt, limit_buy_order_usdt_open, limit_buy_order_usdt, fee, is_short,
limit_sell_order_usdt_open, mocker, order_book_l2, caplog) -> None: limit_sell_order_usdt_open, mocker, order_book_l2, caplog) -> None:
@ -4263,7 +4263,7 @@ def test_startup_trade_reinit(default_conf_usdt, edge_conf, mocker):
assert reinit_mock.call_count == 0 assert reinit_mock.call_count == 0
@ pytest.mark.usefixtures("init_persistence") @pytest.mark.usefixtures("init_persistence")
def test_sync_wallet_dry_run(mocker, default_conf_usdt, ticker_usdt, fee, limit_buy_order_usdt_open, def test_sync_wallet_dry_run(mocker, default_conf_usdt, ticker_usdt, fee, limit_buy_order_usdt_open,
caplog): caplog):
default_conf_usdt['dry_run'] = True default_conf_usdt['dry_run'] = True
@ -4296,8 +4296,8 @@ def test_sync_wallet_dry_run(mocker, default_conf_usdt, ticker_usdt, fee, limit_
caplog) caplog)
@ pytest.mark.usefixtures("init_persistence") @pytest.mark.usefixtures("init_persistence")
@ pytest.mark.parametrize("is_short,buy_calls,sell_calls", [ @pytest.mark.parametrize("is_short,buy_calls,sell_calls", [
(False, 1, 2), (False, 1, 2),
(True, 2, 1), (True, 2, 1),
]) ])
@ -4325,8 +4325,8 @@ def test_cancel_all_open_orders(mocker, default_conf_usdt, fee, limit_order, lim
assert sell_mock.call_count == sell_calls assert sell_mock.call_count == sell_calls
@ pytest.mark.usefixtures("init_persistence") @pytest.mark.usefixtures("init_persistence")
@ pytest.mark.parametrize("is_short", [False, True]) @pytest.mark.parametrize("is_short", [False, True])
def test_check_for_open_trades(mocker, default_conf_usdt, fee, is_short): def test_check_for_open_trades(mocker, default_conf_usdt, fee, is_short):
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
@ -4343,8 +4343,8 @@ def test_check_for_open_trades(mocker, default_conf_usdt, fee, is_short):
assert 'Handle these trades manually' in freqtrade.rpc.send_msg.call_args[0][0]['status'] assert 'Handle these trades manually' in freqtrade.rpc.send_msg.call_args[0][0]['status']
@ pytest.mark.parametrize("is_short", [False, True]) @pytest.mark.parametrize("is_short", [False, True])
@ pytest.mark.usefixtures("init_persistence") @pytest.mark.usefixtures("init_persistence")
def test_startup_update_open_orders(mocker, default_conf_usdt, fee, caplog, is_short): def test_startup_update_open_orders(mocker, default_conf_usdt, fee, caplog, is_short):
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
create_mock_trades(fee, is_short=is_short) create_mock_trades(fee, is_short=is_short)
@ -4370,8 +4370,8 @@ def test_startup_update_open_orders(mocker, default_conf_usdt, fee, caplog, is_s
assert len(Order.get_open_orders()) == 2 assert len(Order.get_open_orders()) == 2
@ pytest.mark.usefixtures("init_persistence") @pytest.mark.usefixtures("init_persistence")
@ pytest.mark.parametrize("is_short", [False, True]) @pytest.mark.parametrize("is_short", [False, True])
def test_update_closed_trades_without_assigned_fees(mocker, default_conf_usdt, fee, is_short): def test_update_closed_trades_without_assigned_fees(mocker, default_conf_usdt, fee, is_short):
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
@ -4434,8 +4434,8 @@ def test_update_closed_trades_without_assigned_fees(mocker, default_conf_usdt, f
assert trade.fee_close_currency is not None assert trade.fee_close_currency is not None
@ pytest.mark.usefixtures("init_persistence") @pytest.mark.usefixtures("init_persistence")
@ pytest.mark.parametrize("is_short", [False, True]) @pytest.mark.parametrize("is_short", [False, True])
def test_reupdate_enter_order_fees(mocker, default_conf_usdt, fee, caplog, is_short): def test_reupdate_enter_order_fees(mocker, default_conf_usdt, fee, caplog, is_short):
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
mock_uts = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.update_trade_state') mock_uts = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.update_trade_state')
@ -4483,7 +4483,7 @@ def test_reupdate_enter_order_fees(mocker, default_conf_usdt, fee, caplog, is_sh
r".* for order .*\.", caplog) r".* for order .*\.", caplog)
@ pytest.mark.usefixtures("init_persistence") @pytest.mark.usefixtures("init_persistence")
def test_handle_insufficient_funds(mocker, default_conf_usdt, fee): def test_handle_insufficient_funds(mocker, default_conf_usdt, fee):
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
mock_rlo = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.refind_lost_order') mock_rlo = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.refind_lost_order')
@ -4521,8 +4521,8 @@ def test_handle_insufficient_funds(mocker, default_conf_usdt, fee):
assert mock_bof.call_count == 1 assert mock_bof.call_count == 1
@ pytest.mark.usefixtures("init_persistence") @pytest.mark.usefixtures("init_persistence")
@ pytest.mark.parametrize("is_short", [False, True]) @pytest.mark.parametrize("is_short", [False, True])
def test_refind_lost_order(mocker, default_conf_usdt, fee, caplog, is_short): def test_refind_lost_order(mocker, default_conf_usdt, fee, caplog, is_short):
caplog.set_level(logging.DEBUG) caplog.set_level(logging.DEBUG)
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)