containing results for all strategies
@@ -165,6 +174,17 @@ def generate_strategy_metrics(all_results: Dict) -> List[Dict]:
tabular_data.append(_generate_result_line(
results['results'], results['config']['dry_run_wallet'], strategy)
)
+ try:
+ max_drawdown_per, _, _, _, _ = calculate_max_drawdown(results['results'],
+ value_col='profit_ratio')
+ max_drawdown_abs, _, _, _, _ = calculate_max_drawdown(results['results'],
+ value_col='profit_abs')
+ except ValueError:
+ max_drawdown_per = 0
+ max_drawdown_abs = 0
+ tabular_data[-1]['max_drawdown_per'] = round(max_drawdown_per * 100, 2)
+ tabular_data[-1]['max_drawdown_abs'] = \
+ round_coin_value(max_drawdown_abs, results['config']['stake_currency'], False)
return tabular_data
@@ -194,7 +214,40 @@ def generate_edge_table(results: dict) -> str:
floatfmt=floatfmt, tablefmt="orgtbl", stralign="right") # type: ignore
+def generate_trading_stats(results: DataFrame) -> Dict[str, Any]:
+ """ Generate overall trade statistics """
+ if len(results) == 0:
+ return {
+ 'wins': 0,
+ 'losses': 0,
+ 'draws': 0,
+ 'holding_avg': timedelta(),
+ 'winner_holding_avg': timedelta(),
+ 'loser_holding_avg': timedelta(),
+ }
+
+ winning_trades = results.loc[results['profit_ratio'] > 0]
+ draw_trades = results.loc[results['profit_ratio'] == 0]
+ losing_trades = results.loc[results['profit_ratio'] < 0]
+ zero_duration_trades = len(results.loc[(results['trade_duration'] == 0) &
+ (results['sell_reason'] == 'trailing_stop_loss')])
+
+ return {
+ 'wins': len(winning_trades),
+ 'losses': len(losing_trades),
+ 'draws': len(draw_trades),
+ 'holding_avg': (timedelta(minutes=round(results['trade_duration'].mean()))
+ if not results.empty else timedelta()),
+ 'winner_holding_avg': (timedelta(minutes=round(winning_trades['trade_duration'].mean()))
+ if not winning_trades.empty else timedelta()),
+ 'loser_holding_avg': (timedelta(minutes=round(losing_trades['trade_duration'].mean()))
+ if not losing_trades.empty else timedelta()),
+ 'zero_duration_trades': zero_duration_trades,
+ }
+
+
def generate_daily_stats(results: DataFrame) -> Dict[str, Any]:
+ """ Generate daily statistics """
if len(results) == 0:
return {
'backtest_best_day': 0,
@@ -204,8 +257,6 @@ def generate_daily_stats(results: DataFrame) -> Dict[str, Any]:
'winning_days': 0,
'draw_days': 0,
'losing_days': 0,
- 'winner_holding_avg': timedelta(),
- 'loser_holding_avg': timedelta(),
}
daily_profit_rel = results.resample('1d', on='close_date')['profit_ratio'].sum()
daily_profit = results.resample('1d', on='close_date')['profit_abs'].sum().round(10)
@@ -217,9 +268,6 @@ def generate_daily_stats(results: DataFrame) -> Dict[str, Any]:
draw_days = sum(daily_profit == 0)
losing_days = sum(daily_profit < 0)
- winning_trades = results.loc[results['profit_ratio'] > 0]
- losing_trades = results.loc[results['profit_ratio'] < 0]
-
return {
'backtest_best_day': best_rel,
'backtest_worst_day': worst_rel,
@@ -228,16 +276,152 @@ def generate_daily_stats(results: DataFrame) -> Dict[str, Any]:
'winning_days': winning_days,
'draw_days': draw_days,
'losing_days': losing_days,
- 'winner_holding_avg': (timedelta(minutes=round(winning_trades['trade_duration'].mean()))
- if not winning_trades.empty else timedelta()),
- 'loser_holding_avg': (timedelta(minutes=round(losing_trades['trade_duration'].mean()))
- if not losing_trades.empty else timedelta()),
}
+def generate_strategy_stats(btdata: Dict[str, DataFrame],
+ strategy: str,
+ content: Dict[str, Any],
+ min_date: datetime, max_date: datetime,
+ market_change: float
+ ) -> Dict[str, Any]:
+ """
+ :param btdata: Backtest data
+ :param strategy: Strategy name
+ :param content: Backtest result data in the format:
+ {'results: results, 'config: config}}.
+ :param min_date: Backtest start date
+ :param max_date: Backtest end date
+ :param market_change: float indicating the market change
+ :return: Dictionary containing results per strategy and a stratgy summary.
+ """
+ results: Dict[str, DataFrame] = content['results']
+ if not isinstance(results, DataFrame):
+ return {}
+ config = content['config']
+ max_open_trades = min(config['max_open_trades'], len(btdata.keys()))
+ starting_balance = config['dry_run_wallet']
+ stake_currency = config['stake_currency']
+
+ pair_results = generate_pair_metrics(btdata, stake_currency=stake_currency,
+ starting_balance=starting_balance,
+ results=results, skip_nan=False)
+ sell_reason_stats = generate_sell_reason_stats(max_open_trades=max_open_trades,
+ results=results)
+ left_open_results = generate_pair_metrics(btdata, stake_currency=stake_currency,
+ starting_balance=starting_balance,
+ results=results.loc[results['is_open']],
+ skip_nan=True)
+ daily_stats = generate_daily_stats(results)
+ trade_stats = generate_trading_stats(results)
+ best_pair = max([pair for pair in pair_results if pair['key'] != 'TOTAL'],
+ key=lambda x: x['profit_sum']) if len(pair_results) > 1 else None
+ worst_pair = min([pair for pair in pair_results if pair['key'] != 'TOTAL'],
+ key=lambda x: x['profit_sum']) if len(pair_results) > 1 else None
+ results['open_timestamp'] = results['open_date'].astype(int64) // 1e6
+ results['close_timestamp'] = results['close_date'].astype(int64) // 1e6
+
+ backtest_days = (max_date - min_date).days
+ strat_stats = {
+ 'trades': results.to_dict(orient='records'),
+ 'locks': [lock.to_json() for lock in content['locks']],
+ 'best_pair': best_pair,
+ 'worst_pair': worst_pair,
+ 'results_per_pair': pair_results,
+ 'sell_reason_summary': sell_reason_stats,
+ 'left_open_trades': left_open_results,
+ 'total_trades': len(results),
+ 'total_volume': float(results['stake_amount'].sum()),
+ 'avg_stake_amount': results['stake_amount'].mean() if len(results) > 0 else 0,
+ 'profit_mean': results['profit_ratio'].mean() if len(results) > 0 else 0,
+ 'profit_median': results['profit_ratio'].median() if len(results) > 0 else 0,
+ 'profit_total': results['profit_abs'].sum() / starting_balance,
+ 'profit_total_abs': results['profit_abs'].sum(),
+ 'backtest_start': min_date.strftime(DATETIME_PRINT_FORMAT),
+ 'backtest_start_ts': int(min_date.timestamp() * 1000),
+ 'backtest_end': max_date.strftime(DATETIME_PRINT_FORMAT),
+ 'backtest_end_ts': int(max_date.timestamp() * 1000),
+ 'backtest_days': backtest_days,
+
+ 'backtest_run_start_ts': content['backtest_start_time'],
+ 'backtest_run_end_ts': content['backtest_end_time'],
+
+ 'trades_per_day': round(len(results) / backtest_days, 2) if backtest_days > 0 else 0,
+ 'market_change': market_change,
+ 'pairlist': list(btdata.keys()),
+ 'stake_amount': config['stake_amount'],
+ 'stake_currency': config['stake_currency'],
+ 'stake_currency_decimals': decimals_per_coin(config['stake_currency']),
+ 'starting_balance': starting_balance,
+ 'dry_run_wallet': starting_balance,
+ 'final_balance': content['final_balance'],
+ 'rejected_signals': content['rejected_signals'],
+ 'max_open_trades': max_open_trades,
+ 'max_open_trades_setting': (config['max_open_trades']
+ if config['max_open_trades'] != float('inf') else -1),
+ 'timeframe': config['timeframe'],
+ 'timerange': config.get('timerange', ''),
+ 'enable_protections': config.get('enable_protections', False),
+ 'strategy_name': strategy,
+ # Parameters relevant for backtesting
+ 'stoploss': config['stoploss'],
+ 'trailing_stop': config.get('trailing_stop', False),
+ 'trailing_stop_positive': config.get('trailing_stop_positive'),
+ 'trailing_stop_positive_offset': config.get('trailing_stop_positive_offset', 0.0),
+ 'trailing_only_offset_is_reached': config.get('trailing_only_offset_is_reached', False),
+ 'use_custom_stoploss': config.get('use_custom_stoploss', False),
+ 'minimal_roi': config['minimal_roi'],
+ 'use_sell_signal': config['ask_strategy']['use_sell_signal'],
+ 'sell_profit_only': config['ask_strategy']['sell_profit_only'],
+ 'sell_profit_offset': config['ask_strategy']['sell_profit_offset'],
+ 'ignore_roi_if_buy_signal': config['ask_strategy']['ignore_roi_if_buy_signal'],
+ **daily_stats,
+ **trade_stats
+ }
+
+ try:
+ max_drawdown, _, _, _, _ = calculate_max_drawdown(
+ results, value_col='profit_ratio')
+ drawdown_abs, drawdown_start, drawdown_end, high_val, low_val = calculate_max_drawdown(
+ results, value_col='profit_abs')
+ strat_stats.update({
+ 'max_drawdown': max_drawdown,
+ 'max_drawdown_abs': drawdown_abs,
+ 'drawdown_start': drawdown_start.strftime(DATETIME_PRINT_FORMAT),
+ 'drawdown_start_ts': drawdown_start.timestamp() * 1000,
+ 'drawdown_end': drawdown_end.strftime(DATETIME_PRINT_FORMAT),
+ 'drawdown_end_ts': drawdown_end.timestamp() * 1000,
+
+ 'max_drawdown_low': low_val,
+ 'max_drawdown_high': high_val,
+ })
+
+ csum_min, csum_max = calculate_csum(results, starting_balance)
+ strat_stats.update({
+ 'csum_min': csum_min,
+ 'csum_max': csum_max
+ })
+
+ except ValueError:
+ strat_stats.update({
+ 'max_drawdown': 0.0,
+ 'max_drawdown_abs': 0.0,
+ 'max_drawdown_low': 0.0,
+ 'max_drawdown_high': 0.0,
+ 'drawdown_start': datetime(1970, 1, 1, tzinfo=timezone.utc),
+ 'drawdown_start_ts': 0,
+ 'drawdown_end': datetime(1970, 1, 1, tzinfo=timezone.utc),
+ 'drawdown_end_ts': 0,
+ 'csum_min': 0,
+ 'csum_max': 0
+ })
+
+ return strat_stats
+
+
def generate_backtest_stats(btdata: Dict[str, DataFrame],
all_results: Dict[str, Dict[str, Union[DataFrame, Dict]]],
- min_date: Arrow, max_date: Arrow
+ min_date: datetime, max_date: datetime
) -> Dict[str, Any]:
"""
:param btdata: Backtest data
@@ -245,132 +429,17 @@ def generate_backtest_stats(btdata: Dict[str, DataFrame],
{ Strategy: {'results: results, 'config: config}}.
:param min_date: Backtest start date
:param max_date: Backtest end date
- :return:
- Dictionary containing results per strategy and a stratgy summary.
+ :return: Dictionary containing results per strategy and a stratgy summary.
"""
result: Dict[str, Any] = {'strategy': {}}
market_change = calculate_market_change(btdata, 'close')
for strategy, content in all_results.items():
- results: Dict[str, DataFrame] = content['results']
- if not isinstance(results, DataFrame):
- continue
- config = content['config']
- max_open_trades = min(config['max_open_trades'], len(btdata.keys()))
- starting_balance = config['dry_run_wallet']
- stake_currency = config['stake_currency']
-
- pair_results = generate_pair_metrics(btdata, stake_currency=stake_currency,
- starting_balance=starting_balance,
- results=results, skip_nan=False)
- sell_reason_stats = generate_sell_reason_stats(max_open_trades=max_open_trades,
- results=results)
- left_open_results = generate_pair_metrics(btdata, stake_currency=stake_currency,
- starting_balance=starting_balance,
- results=results.loc[results['is_open']],
- skip_nan=True)
- daily_stats = generate_daily_stats(results)
- best_pair = max([pair for pair in pair_results if pair['key'] != 'TOTAL'],
- key=lambda x: x['profit_sum']) if len(pair_results) > 1 else None
- worst_pair = min([pair for pair in pair_results if pair['key'] != 'TOTAL'],
- key=lambda x: x['profit_sum']) if len(pair_results) > 1 else None
- results['open_timestamp'] = results['open_date'].astype(int64) // 1e6
- results['close_timestamp'] = results['close_date'].astype(int64) // 1e6
-
- backtest_days = (max_date - min_date).days
- strat_stats = {
- 'trades': results.to_dict(orient='records'),
- 'locks': [lock.to_json() for lock in content['locks']],
- 'best_pair': best_pair,
- 'worst_pair': worst_pair,
- 'results_per_pair': pair_results,
- 'sell_reason_summary': sell_reason_stats,
- 'left_open_trades': left_open_results,
- 'total_trades': len(results),
- 'total_volume': float(results['stake_amount'].sum()),
- 'avg_stake_amount': results['stake_amount'].mean() if len(results) > 0 else 0,
- 'profit_mean': results['profit_ratio'].mean() if len(results) > 0 else 0,
- 'profit_total': results['profit_abs'].sum() / starting_balance,
- 'profit_total_abs': results['profit_abs'].sum(),
- 'backtest_start': min_date.datetime,
- 'backtest_start_ts': min_date.int_timestamp * 1000,
- 'backtest_end': max_date.datetime,
- 'backtest_end_ts': max_date.int_timestamp * 1000,
- 'backtest_days': backtest_days,
-
- 'backtest_run_start_ts': content['backtest_start_time'],
- 'backtest_run_end_ts': content['backtest_end_time'],
-
- 'trades_per_day': round(len(results) / backtest_days, 2) if backtest_days > 0 else 0,
- 'market_change': market_change,
- 'pairlist': list(btdata.keys()),
- 'stake_amount': config['stake_amount'],
- 'stake_currency': config['stake_currency'],
- 'stake_currency_decimals': decimals_per_coin(config['stake_currency']),
- 'starting_balance': starting_balance,
- 'dry_run_wallet': starting_balance,
- 'final_balance': content['final_balance'],
- 'max_open_trades': max_open_trades,
- 'max_open_trades_setting': (config['max_open_trades']
- if config['max_open_trades'] != float('inf') else -1),
- 'timeframe': config['timeframe'],
- 'timerange': config.get('timerange', ''),
- 'enable_protections': config.get('enable_protections', False),
- 'strategy_name': strategy,
- # Parameters relevant for backtesting
- 'stoploss': config['stoploss'],
- 'trailing_stop': config.get('trailing_stop', False),
- 'trailing_stop_positive': config.get('trailing_stop_positive'),
- 'trailing_stop_positive_offset': config.get('trailing_stop_positive_offset', 0.0),
- 'trailing_only_offset_is_reached': config.get('trailing_only_offset_is_reached', False),
- 'use_custom_stoploss': config.get('use_custom_stoploss', False),
- 'minimal_roi': config['minimal_roi'],
- 'use_sell_signal': config['ask_strategy']['use_sell_signal'],
- 'sell_profit_only': config['ask_strategy']['sell_profit_only'],
- 'sell_profit_offset': config['ask_strategy']['sell_profit_offset'],
- 'ignore_roi_if_buy_signal': config['ask_strategy']['ignore_roi_if_buy_signal'],
- **daily_stats,
- }
+ strat_stats = generate_strategy_stats(btdata, strategy, content,
+ min_date, max_date, market_change=market_change)
result['strategy'][strategy] = strat_stats
- try:
- max_drawdown, _, _, _, _ = calculate_max_drawdown(
- results, value_col='profit_ratio')
- drawdown_abs, drawdown_start, drawdown_end, high_val, low_val = calculate_max_drawdown(
- results, value_col='profit_abs')
- strat_stats.update({
- 'max_drawdown': max_drawdown,
- 'max_drawdown_abs': drawdown_abs,
- 'drawdown_start': drawdown_start,
- 'drawdown_start_ts': drawdown_start.timestamp() * 1000,
- 'drawdown_end': drawdown_end,
- 'drawdown_end_ts': drawdown_end.timestamp() * 1000,
-
- 'max_drawdown_low': low_val,
- 'max_drawdown_high': high_val,
- })
-
- csum_min, csum_max = calculate_csum(results, starting_balance)
- strat_stats.update({
- 'csum_min': csum_min,
- 'csum_max': csum_max
- })
-
- except ValueError:
- strat_stats.update({
- 'max_drawdown': 0.0,
- 'max_drawdown_abs': 0.0,
- 'max_drawdown_low': 0.0,
- 'max_drawdown_high': 0.0,
- 'drawdown_start': datetime(1970, 1, 1, tzinfo=timezone.utc),
- 'drawdown_start_ts': 0,
- 'drawdown_end': datetime(1970, 1, 1, tzinfo=timezone.utc),
- 'drawdown_end_ts': 0,
- 'csum_min': 0,
- 'csum_max': 0
- })
-
- strategy_results = generate_strategy_metrics(all_results=all_results)
+ strategy_results = generate_strategy_comparison(all_results=all_results)
result['strategy_comparison'] = strategy_results
@@ -393,7 +462,8 @@ def text_table_bt_results(pair_results: List[Dict[str, Any]], stake_currency: st
floatfmt = _get_line_floatfmt(stake_currency)
output = [[
t['key'], t['trades'], t['profit_mean_pct'], t['profit_sum_pct'], t['profit_total_abs'],
- t['profit_total_pct'], t['duration_avg'], t['wins'], t['draws'], t['losses']
+ t['profit_total_pct'], t['duration_avg'],
+ _generate_wins_draws_losses(t['wins'], t['draws'], t['losses'])
] for t in pair_results]
# Ignore type as floatfmt does allow tuples but mypy does not know that
return tabulate(output, headers=headers,
@@ -410,9 +480,7 @@ def text_table_sell_reason(sell_reason_stats: List[Dict[str, Any]], stake_curren
headers = [
'Sell Reason',
'Sells',
- 'Wins',
- 'Draws',
- 'Losses',
+ 'Win Draws Loss Win%',
'Avg Profit %',
'Cum Profit %',
f'Tot Profit {stake_currency}',
@@ -420,7 +488,8 @@ def text_table_sell_reason(sell_reason_stats: List[Dict[str, Any]], stake_curren
]
output = [[
- t['sell_reason'], t['trades'], t['wins'], t['draws'], t['losses'],
+ t['sell_reason'], t['trades'],
+ _generate_wins_draws_losses(t['wins'], t['draws'], t['losses']),
t['profit_mean_pct'], t['profit_sum_pct'],
round_coin_value(t['profit_total_abs'], stake_currency, False),
t['profit_total_pct'],
@@ -438,11 +507,22 @@ def text_table_strategy(strategy_results, stake_currency: str) -> str:
"""
floatfmt = _get_line_floatfmt(stake_currency)
headers = _get_line_header('Strategy', stake_currency)
+ # _get_line_header() is also used for per-pair summary. Per-pair drawdown is mostly useless
+ # therefore we slip this column in only for strategy summary here.
+ headers.append('Drawdown')
+
+ # Align drawdown string on the center two space separator.
+ drawdown = [f'{t["max_drawdown_per"]:.2f}' for t in strategy_results]
+ dd_pad_abs = max([len(t['max_drawdown_abs']) for t in strategy_results])
+ dd_pad_per = max([len(dd) for dd in drawdown])
+ drawdown = [f'{t["max_drawdown_abs"]:>{dd_pad_abs}} {stake_currency} {dd:>{dd_pad_per}}%'
+ for t, dd in zip(strategy_results, drawdown)]
output = [[
t['key'], t['trades'], t['profit_mean_pct'], t['profit_sum_pct'], t['profit_total_abs'],
- t['profit_total_pct'], t['duration_avg'], t['wins'], t['draws'], t['losses']
- ] for t in strategy_results]
+ t['profit_total_pct'], t['duration_avg'],
+ _generate_wins_draws_losses(t['wins'], t['draws'], t['losses']), drawdown]
+ for t, drawdown in zip(strategy_results, drawdown)]
# Ignore type as floatfmt does allow tuples but mypy does not know that
return tabulate(output, headers=headers,
floatfmt=floatfmt, tablefmt="orgtbl", stralign="right")
@@ -452,9 +532,21 @@ def text_table_add_metrics(strat_results: Dict) -> str:
if len(strat_results['trades']) > 0:
best_trade = max(strat_results['trades'], key=lambda x: x['profit_ratio'])
worst_trade = min(strat_results['trades'], key=lambda x: x['profit_ratio'])
+
+ # Newly added fields should be ignored if they are missing in strat_results. hyperopt-show
+ # command stores these results and newer version of freqtrade must be able to handle old
+ # results with missing new fields.
+ zero_duration_trades = '--'
+
+ if 'zero_duration_trades' in strat_results:
+ zero_duration_trades_per = \
+ 100.0 / strat_results['total_trades'] * strat_results['zero_duration_trades']
+ zero_duration_trades = f'{zero_duration_trades_per:.2f}% ' \
+ f'({strat_results["zero_duration_trades"]})'
+
metrics = [
- ('Backtesting from', strat_results['backtest_start'].strftime(DATETIME_PRINT_FORMAT)),
- ('Backtesting to', strat_results['backtest_end'].strftime(DATETIME_PRINT_FORMAT)),
+ ('Backtesting from', strat_results['backtest_start']),
+ ('Backtesting to', strat_results['backtest_end']),
('Max open trades', strat_results['max_open_trades']),
('', ''), # Empty line to improve readability
('Total trades', strat_results['total_trades']),
@@ -464,13 +556,12 @@ def text_table_add_metrics(strat_results: Dict) -> str:
strat_results['stake_currency'])),
('Absolute profit ', round_coin_value(strat_results['profit_total_abs'],
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. stake amount', round_coin_value(strat_results['avg_stake_amount'],
strat_results['stake_currency'])),
('Total trade volume', round_coin_value(strat_results['total_volume'],
strat_results['stake_currency'])),
-
('', ''), # Empty line to improve readability
('Best Pair', f"{strat_results['best_pair']['key']} "
f"{round(strat_results['best_pair']['profit_sum_pct'], 2)}%"),
@@ -488,6 +579,8 @@ def text_table_add_metrics(strat_results: Dict) -> str:
f"{strat_results['draw_days']} / {strat_results['losing_days']}"),
('Avg. Duration Winners', f"{strat_results['winner_holding_avg']}"),
('Avg. Duration Loser', f"{strat_results['loser_holding_avg']}"),
+ ('Zero Duration Trades', zero_duration_trades),
+ ('Rejected Buy signals', strat_results.get('rejected_signals', 'N/A')),
('', ''), # Empty line to improve readability
('Min balance', round_coin_value(strat_results['csum_min'],
@@ -502,8 +595,8 @@ def text_table_add_metrics(strat_results: Dict) -> str:
strat_results['stake_currency'])),
('Drawdown low', round_coin_value(strat_results['max_drawdown_low'],
strat_results['stake_currency'])),
- ('Drawdown Start', strat_results['drawdown_start'].strftime(DATETIME_PRINT_FORMAT)),
- ('Drawdown End', strat_results['drawdown_end'].strftime(DATETIME_PRINT_FORMAT)),
+ ('Drawdown Start', strat_results['drawdown_start']),
+ ('Drawdown End', strat_results['drawdown_end']),
('Market change', f"{round(strat_results['market_change'] * 100, 2)}%"),
]
@@ -522,37 +615,43 @@ def text_table_add_metrics(strat_results: Dict) -> str:
return message
+def show_backtest_result(strategy: str, results: Dict[str, Any], stake_currency: str):
+ """
+ Print results for one strategy
+ """
+ # Print results
+ print(f"Result for strategy {strategy}")
+ table = text_table_bt_results(results['results_per_pair'], stake_currency=stake_currency)
+ if isinstance(table, str):
+ print(' BACKTESTING REPORT '.center(len(table.splitlines()[0]), '='))
+ print(table)
+
+ table = text_table_sell_reason(sell_reason_stats=results['sell_reason_summary'],
+ stake_currency=stake_currency)
+ if isinstance(table, str) and len(table) > 0:
+ print(' SELL REASON STATS '.center(len(table.splitlines()[0]), '='))
+ print(table)
+
+ table = text_table_bt_results(results['left_open_trades'], stake_currency=stake_currency)
+ if isinstance(table, str) and len(table) > 0:
+ print(' LEFT OPEN TRADES REPORT '.center(len(table.splitlines()[0]), '='))
+ print(table)
+
+ table = text_table_add_metrics(results)
+ if isinstance(table, str) and len(table) > 0:
+ print(' SUMMARY METRICS '.center(len(table.splitlines()[0]), '='))
+ print(table)
+
+ if isinstance(table, str) and len(table) > 0:
+ print('=' * len(table.splitlines()[0]))
+ print()
+
+
def show_backtest_results(config: Dict, backtest_stats: Dict):
stake_currency = config['stake_currency']
for strategy, results in backtest_stats['strategy'].items():
-
- # Print results
- print(f"Result for strategy {strategy}")
- table = text_table_bt_results(results['results_per_pair'], stake_currency=stake_currency)
- if isinstance(table, str):
- print(' BACKTESTING REPORT '.center(len(table.splitlines()[0]), '='))
- print(table)
-
- table = text_table_sell_reason(sell_reason_stats=results['sell_reason_summary'],
- stake_currency=stake_currency)
- if isinstance(table, str) and len(table) > 0:
- print(' SELL REASON STATS '.center(len(table.splitlines()[0]), '='))
- print(table)
-
- table = text_table_bt_results(results['left_open_trades'], stake_currency=stake_currency)
- if isinstance(table, str) and len(table) > 0:
- print(' LEFT OPEN TRADES REPORT '.center(len(table.splitlines()[0]), '='))
- print(table)
-
- table = text_table_add_metrics(results)
- if isinstance(table, str) and len(table) > 0:
- print(' SUMMARY METRICS '.center(len(table.splitlines()[0]), '='))
- print(table)
-
- if isinstance(table, str) and len(table) > 0:
- print('=' * len(table.splitlines()[0]))
- print()
+ show_backtest_result(strategy, results, stake_currency)
if len(backtest_stats['strategy']) > 1:
# Print Strategy summary table
diff --git a/freqtrade/persistence/migrations.py b/freqtrade/persistence/migrations.py
index 961363b0e..d89256baf 100644
--- a/freqtrade/persistence/migrations.py
+++ b/freqtrade/persistence/migrations.py
@@ -123,6 +123,27 @@ def migrate_open_orders_to_trades(engine):
""")
+def migrate_orders_table(decl_base, inspector, engine, table_back_name: str, cols: List):
+ # Schema migration necessary
+ engine.execute(f"alter table orders rename to {table_back_name}")
+ # drop indexes on backup table
+ for index in inspector.get_indexes(table_back_name):
+ engine.execute(f"drop index {index['name']}")
+
+ # let SQLAlchemy create the schema as required
+ decl_base.metadata.create_all(engine)
+
+ engine.execute(f"""
+ insert into orders ( id, ft_trade_id, ft_order_side, ft_pair, ft_is_open, order_id, status,
+ symbol, order_type, side, price, amount, filled, average, remaining, cost, order_date,
+ order_filled_date, order_update_date)
+ select id, ft_trade_id, ft_order_side, ft_pair, ft_is_open, order_id, status,
+ symbol, order_type, side, price, amount, filled, null average, remaining, cost, order_date,
+ order_filled_date, order_update_date
+ from {table_back_name}
+ """)
+
+
def check_migrate(engine, decl_base, previous_tables) -> None:
"""
Checks if migration is necessary and migrates if necessary
@@ -145,6 +166,11 @@ def check_migrate(engine, decl_base, previous_tables) -> None:
logger.info('Moving open orders to Orders table.')
migrate_open_orders_to_trades(engine)
else:
- pass
- # Empty for now - as there is only one iteration of the orders table so far.
- # table_back_name = get_backup_name(tabs, 'orders_bak')
+ cols_order = inspector.get_columns('orders')
+
+ if not has_column(cols_order, 'average'):
+ tabs = get_table_names_for_table(inspector, 'orders')
+ # Empty for now - as there is only one iteration of the orders table so far.
+ table_back_name = get_backup_name(tabs, 'orders_bak')
+
+ migrate_orders_table(decl_base, inspector, engine, table_back_name, cols)
diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py
index afd51366a..f2e7a10c4 100644
--- a/freqtrade/persistence/models.py
+++ b/freqtrade/persistence/models.py
@@ -112,16 +112,17 @@ class Order(_DECL_BASE):
trade = relationship("Trade", back_populates="orders")
- ft_order_side = Column(String, nullable=False)
- ft_pair = Column(String, nullable=False)
+ ft_order_side = Column(String(25), nullable=False)
+ ft_pair = Column(String(25), nullable=False)
ft_is_open = Column(Boolean, nullable=False, default=True, index=True)
- order_id = Column(String, nullable=False, index=True)
- status = Column(String, nullable=True)
- symbol = Column(String, nullable=True)
- order_type = Column(String, nullable=True)
- side = Column(String, nullable=True)
+ order_id = Column(String(255), nullable=False, index=True)
+ status = Column(String(255), nullable=True)
+ symbol = Column(String(25), nullable=True)
+ order_type = Column(String(50), nullable=True)
+ side = Column(String(25), nullable=True)
price = Column(Float, nullable=True)
+ average = Column(Float, nullable=True)
amount = Column(Float, nullable=True)
filled = Column(Float, nullable=True)
remaining = Column(Float, nullable=True)
@@ -150,6 +151,7 @@ class Order(_DECL_BASE):
self.price = order.get('price', self.price)
self.amount = order.get('amount', self.amount)
self.filled = order.get('filled', self.filled)
+ self.average = order.get('average', self.average)
self.remaining = order.get('remaining', self.remaining)
self.cost = order.get('cost', self.cost)
if 'timestamp' in order and order['timestamp'] is not None:
@@ -656,15 +658,15 @@ class Trade(_DECL_BASE, LocalTrade):
orders = relationship("Order", order_by="Order.id", cascade="all, delete-orphan")
- exchange = Column(String, nullable=False)
- pair = Column(String, nullable=False, index=True)
+ exchange = Column(String(25), nullable=False)
+ pair = Column(String(25), nullable=False, index=True)
is_open = Column(Boolean, nullable=False, default=True, index=True)
fee_open = Column(Float, nullable=False, default=0.0)
fee_open_cost = Column(Float, nullable=True)
- fee_open_currency = Column(String, nullable=True)
+ fee_open_currency = Column(String(25), nullable=True)
fee_close = Column(Float, nullable=False, default=0.0)
fee_close_cost = Column(Float, nullable=True)
- fee_close_currency = Column(String, nullable=True)
+ fee_close_currency = Column(String(25), nullable=True)
open_rate = Column(Float)
open_rate_requested = Column(Float)
# open_trade_value - calculated via _calc_open_trade_value
@@ -678,7 +680,7 @@ class Trade(_DECL_BASE, LocalTrade):
amount_requested = Column(Float)
open_date = Column(DateTime, nullable=False, default=datetime.utcnow)
close_date = Column(DateTime)
- open_order_id = Column(String)
+ open_order_id = Column(String(255))
# absolute value of the stop loss
stop_loss = Column(Float, nullable=True, default=0.0)
# percentage value of the stop loss
@@ -688,16 +690,16 @@ class Trade(_DECL_BASE, LocalTrade):
# percentage value of the initial stop loss
initial_stop_loss_pct = Column(Float, nullable=True)
# stoploss order id which is on exchange
- stoploss_order_id = Column(String, nullable=True, index=True)
+ stoploss_order_id = Column(String(255), nullable=True, index=True)
# last update time of the stoploss order on exchange
stoploss_last_update = Column(DateTime, nullable=True)
# absolute value of the highest reached price
max_rate = Column(Float, nullable=True, default=0.0)
# Lowest price reached
min_rate = Column(Float, nullable=True)
- sell_reason = Column(String, nullable=True)
- sell_order_status = Column(String, nullable=True)
- strategy = Column(String, nullable=True)
+ sell_reason = Column(String(100), nullable=True)
+ sell_order_status = Column(String(100), nullable=True)
+ strategy = Column(String(100), nullable=True)
timeframe = Column(Integer, nullable=True)
def __init__(self, **kwargs):
@@ -815,18 +817,20 @@ class Trade(_DECL_BASE, LocalTrade):
pair_rates = Trade.query.with_entities(
Trade.pair,
func.sum(Trade.close_profit).label('profit_sum'),
+ func.sum(Trade.close_profit_abs).label('profit_sum_abs'),
func.count(Trade.pair).label('count')
).filter(Trade.is_open.is_(False))\
.group_by(Trade.pair) \
- .order_by(desc('profit_sum')) \
+ .order_by(desc('profit_sum_abs')) \
.all()
return [
{
'pair': pair,
- 'profit': rate,
+ 'profit': profit,
+ 'profit_abs': profit_abs,
'count': count
}
- for pair, rate, count in pair_rates
+ for pair, profit, profit_abs, count in pair_rates
]
@staticmethod
@@ -852,8 +856,8 @@ class PairLock(_DECL_BASE):
id = Column(Integer, primary_key=True)
- pair = Column(String, nullable=False, index=True)
- reason = Column(String, nullable=True)
+ pair = Column(String(25), nullable=False, index=True)
+ reason = Column(String(255), nullable=True)
# Time the pair was locked (start time)
lock_time = Column(DateTime, nullable=False)
# Time until the pair is locked (end time)
diff --git a/freqtrade/plot/plotting.py b/freqtrade/plot/plotting.py
index d5a729ee1..bb4283406 100644
--- a/freqtrade/plot/plotting.py
+++ b/freqtrade/plot/plotting.py
@@ -77,7 +77,8 @@ def init_plotscript(config, markets: List, startup_candles: int = 0):
)
except ValueError as e:
raise OperationalException(e) from e
- trades = trim_dataframe(trades, timerange, 'open_date')
+ if not trades.empty:
+ trades = trim_dataframe(trades, timerange, 'open_date')
return {"ohlcv": data,
"trades": trades,
@@ -540,8 +541,11 @@ def load_and_plot_trades(config: Dict[str, Any]):
df_analyzed = strategy.analyze_ticker(data, {'pair': pair})
df_analyzed = trim_dataframe(df_analyzed, timerange)
- trades_pair = trades.loc[trades['pair'] == pair]
- trades_pair = extract_trades_of_period(df_analyzed, trades_pair)
+ if not trades.empty:
+ trades_pair = trades.loc[trades['pair'] == pair]
+ trades_pair = extract_trades_of_period(df_analyzed, trades_pair)
+ else:
+ trades_pair = trades
fig = generate_candlestick_graph(
pair=pair,
diff --git a/freqtrade/plugins/pairlist/AgeFilter.py b/freqtrade/plugins/pairlist/AgeFilter.py
index 8a5379ca6..8f623b062 100644
--- a/freqtrade/plugins/pairlist/AgeFilter.py
+++ b/freqtrade/plugins/pairlist/AgeFilter.py
@@ -71,14 +71,14 @@ class AgeFilter(IPairList):
daily_candles = candles[(p, '1d')] if (p, '1d') in candles else None
if not self._validate_pair_loc(p, daily_candles):
pairlist.remove(p)
- logger.info(f"Validated {len(pairlist)} pairs.")
+ self.log_once(f"Validated {len(pairlist)} pairs.", logger.info)
return pairlist
def _validate_pair_loc(self, pair: str, daily_candles: Optional[DataFrame]) -> bool:
"""
Validate age for the ticker
:param pair: Pair that's currently validated
- :param ticker: ticker dict as returned from ccxt.load_markets()
+ :param ticker: ticker dict as returned from ccxt.fetch_tickers()
:return: True if the pair can stay, false if it should be removed
"""
# Check symbol in cache
@@ -86,7 +86,7 @@ class AgeFilter(IPairList):
return True
if daily_candles is not None:
- if len(daily_candles) > self._min_days_listed:
+ if len(daily_candles) >= self._min_days_listed:
# We have fetched at least the minimum required number of daily candles
# Add to cache, store the time we last checked this symbol
self._symbolsChecked[pair] = int(arrow.utcnow().float_timestamp) * 1000
diff --git a/freqtrade/plugins/pairlist/IPairList.py b/freqtrade/plugins/pairlist/IPairList.py
index 3e6252fc4..74348b1a7 100644
--- a/freqtrade/plugins/pairlist/IPairList.py
+++ b/freqtrade/plugins/pairlist/IPairList.py
@@ -7,7 +7,7 @@ from copy import deepcopy
from typing import Any, Dict, List
from freqtrade.exceptions import OperationalException
-from freqtrade.exchange import market_is_active
+from freqtrade.exchange import Exchange, market_is_active
from freqtrade.mixins import LoggingMixin
@@ -16,7 +16,7 @@ logger = logging.getLogger(__name__)
class IPairList(LoggingMixin, ABC):
- def __init__(self, exchange, pairlistmanager,
+ def __init__(self, exchange: Exchange, pairlistmanager,
config: Dict[str, Any], pairlistconfig: Dict[str, Any],
pairlist_pos: int) -> None:
"""
@@ -28,7 +28,7 @@ class IPairList(LoggingMixin, ABC):
"""
self._enabled = True
- self._exchange = exchange
+ self._exchange: Exchange = exchange
self._pairlistmanager = pairlistmanager
self._config = config
self._pairlistconfig = pairlistconfig
@@ -68,7 +68,7 @@ class IPairList(LoggingMixin, ABC):
filter_pairlist() method.
:param pair: Pair that's currently validated
- :param ticker: ticker dict as returned from ccxt.load_markets()
+ :param ticker: ticker dict as returned from ccxt.fetch_tickers()
:return: True if the pair can stay, false if it should be removed
"""
raise NotImplementedError()
diff --git a/freqtrade/plugins/pairlist/PerformanceFilter.py b/freqtrade/plugins/pairlist/PerformanceFilter.py
index 73a9436fa..bf474cb21 100644
--- a/freqtrade/plugins/pairlist/PerformanceFilter.py
+++ b/freqtrade/plugins/pairlist/PerformanceFilter.py
@@ -39,7 +39,12 @@ class PerformanceFilter(IPairList):
:return: new allowlist
"""
# Get the trading performance for pairs from database
- performance = pd.DataFrame(Trade.get_overall_performance())
+ try:
+ performance = pd.DataFrame(Trade.get_overall_performance())
+ except AttributeError:
+ # Performancefilter does not work in backtesting.
+ self.log_once("PerformanceFilter is not available in this mode.", logger.warning)
+ return pairlist
# Skip performance-based sorting if no performance data is available
if len(performance) == 0:
diff --git a/freqtrade/plugins/pairlist/PrecisionFilter.py b/freqtrade/plugins/pairlist/PrecisionFilter.py
index 519337f29..a3c262e8c 100644
--- a/freqtrade/plugins/pairlist/PrecisionFilter.py
+++ b/freqtrade/plugins/pairlist/PrecisionFilter.py
@@ -48,7 +48,7 @@ class PrecisionFilter(IPairList):
Check if pair has enough room to add a stoploss to avoid "unsellable" buys of very
low value pairs.
:param pair: Pair that's currently validated
- :param ticker: ticker dict as returned from ccxt.load_markets()
+ :param ticker: ticker dict as returned from ccxt.fetch_tickers()
:return: True if the pair can stay, false if it should be removed
"""
stop_price = ticker['ask'] * self._stoploss
diff --git a/freqtrade/plugins/pairlist/PriceFilter.py b/freqtrade/plugins/pairlist/PriceFilter.py
index a0579b196..5b5afb557 100644
--- a/freqtrade/plugins/pairlist/PriceFilter.py
+++ b/freqtrade/plugins/pairlist/PriceFilter.py
@@ -27,9 +27,13 @@ class PriceFilter(IPairList):
self._max_price = pairlistconfig.get('max_price', 0)
if self._max_price < 0:
raise OperationalException("PriceFilter requires max_price to be >= 0")
+ self._max_value = pairlistconfig.get('max_value', 0)
+ if self._max_value < 0:
+ raise OperationalException("PriceFilter requires max_value to be >= 0")
self._enabled = ((self._low_price_ratio > 0) or
(self._min_price > 0) or
- (self._max_price > 0))
+ (self._max_price > 0) or
+ (self._max_value > 0))
@property
def needstickers(self) -> bool:
@@ -51,6 +55,8 @@ class PriceFilter(IPairList):
active_price_filters.append(f"below {self._min_price:.8f}")
if self._max_price != 0:
active_price_filters.append(f"above {self._max_price:.8f}")
+ if self._max_value != 0:
+ active_price_filters.append(f"Value above {self._max_value:.8f}")
if len(active_price_filters):
return f"{self.name} - Filtering pairs priced {' or '.join(active_price_filters)}."
@@ -61,7 +67,7 @@ class PriceFilter(IPairList):
"""
Check if if one price-step (pip) is > than a certain barrier.
:param pair: Pair that's currently validated
- :param ticker: ticker dict as returned from ccxt.load_markets()
+ :param ticker: ticker dict as returned from ccxt.fetch_tickers()
:return: True if the pair can stay, false if it should be removed
"""
if ticker.get('last', None) is None or ticker.get('last') == 0:
@@ -79,6 +85,32 @@ class PriceFilter(IPairList):
f"because 1 unit is {changeperc * 100:.3f}%", logger.info)
return False
+ # Perform low_amount check
+ if self._max_value != 0:
+ price = ticker['last']
+ market = self._exchange.markets[pair]
+ limits = market['limits']
+ if ('amount' in limits and 'min' in limits['amount']
+ and limits['amount']['min'] is not None):
+ min_amount = limits['amount']['min']
+ min_precision = market['precision']['amount']
+
+ min_value = min_amount * price
+ if self._exchange.precisionMode == 4:
+ # tick size
+ next_value = (min_amount + min_precision) * price
+ else:
+ # Decimal places
+ min_precision = pow(0.1, min_precision)
+ next_value = (min_amount + min_precision) * price
+ diff = next_value - min_value
+
+ if diff > self._max_value:
+ self.log_once(f"Removed {pair} from whitelist, "
+ f"because min value change of {diff} > {self._max_value}.",
+ logger.info)
+ return False
+
# Perform min_price check.
if self._min_price != 0:
if ticker['last'] < self._min_price:
@@ -89,7 +121,7 @@ class PriceFilter(IPairList):
# Perform max_price check.
if self._max_price != 0:
if ticker['last'] > self._max_price:
- self.log_once(f"Removed {ticker['symbol']} from whitelist, "
+ self.log_once(f"Removed {pair} from whitelist, "
f"because last price > {self._max_price:.8f}", logger.info)
return False
diff --git a/freqtrade/plugins/pairlist/SpreadFilter.py b/freqtrade/plugins/pairlist/SpreadFilter.py
index 9fa211750..1b152774b 100644
--- a/freqtrade/plugins/pairlist/SpreadFilter.py
+++ b/freqtrade/plugins/pairlist/SpreadFilter.py
@@ -40,7 +40,7 @@ class SpreadFilter(IPairList):
"""
Validate spread for the ticker
:param pair: Pair that's currently validated
- :param ticker: ticker dict as returned from ccxt.load_markets()
+ :param ticker: ticker dict as returned from ccxt.fetch_tickers()
:return: True if the pair can stay, false if it should be removed
"""
if 'bid' in ticker and 'ask' in ticker and ticker['ask']:
diff --git a/freqtrade/plugins/pairlist/VolatilityFilter.py b/freqtrade/plugins/pairlist/VolatilityFilter.py
index 400b1577d..bc617a1db 100644
--- a/freqtrade/plugins/pairlist/VolatilityFilter.py
+++ b/freqtrade/plugins/pairlist/VolatilityFilter.py
@@ -90,7 +90,7 @@ class VolatilityFilter(IPairList):
"""
Validate trading range
:param pair: Pair that's currently validated
- :param ticker: ticker dict as returned from ccxt.load_markets()
+ :param ticker: ticker dict as returned from ccxt.fetch_tickers()
:return: True if the pair can stay, false if it should be removed
"""
# Check symbol in cache
diff --git a/freqtrade/plugins/pairlist/rangestabilityfilter.py b/freqtrade/plugins/pairlist/rangestabilityfilter.py
index 6565e92c1..8be61166b 100644
--- a/freqtrade/plugins/pairlist/rangestabilityfilter.py
+++ b/freqtrade/plugins/pairlist/rangestabilityfilter.py
@@ -83,7 +83,7 @@ class RangeStabilityFilter(IPairList):
"""
Validate trading range
:param pair: Pair that's currently validated
- :param ticker: ticker dict as returned from ccxt.load_markets()
+ :param ticker: ticker dict as returned from ccxt.fetch_tickers()
:return: True if the pair can stay, false if it should be removed
"""
# Check symbol in cache
diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py
index e582f6aa8..4d06d3ecf 100644
--- a/freqtrade/rpc/api_server/api_schemas.py
+++ b/freqtrade/rpc/api_server/api_schemas.py
@@ -57,6 +57,7 @@ class Count(BaseModel):
class PerformanceEntry(BaseModel):
pair: str
profit: float
+ profit_abs: float
count: int
@@ -268,7 +269,7 @@ class DeleteTrade(BaseModel):
class PlotConfig_(BaseModel):
main_plot: Dict[str, Any]
- subplots: Optional[Dict[str, Any]]
+ subplots: Dict[str, Any]
class PlotConfig(BaseModel):
diff --git a/freqtrade/rpc/fiat_convert.py b/freqtrade/rpc/fiat_convert.py
index 380070deb..5ae20afa1 100644
--- a/freqtrade/rpc/fiat_convert.py
+++ b/freqtrade/rpc/fiat_convert.py
@@ -3,11 +3,13 @@ Module that define classes to convert Crypto-currency to FIAT
e.g BTC to USD
"""
+import datetime
import logging
from typing import Dict
from cachetools.ttl import TTLCache
from pycoingecko import CoinGeckoAPI
+from requests.exceptions import RequestException
from freqtrade.constants import SUPPORTED_FIAT
@@ -25,6 +27,7 @@ class CryptoToFiatConverter:
_coingekko: CoinGeckoAPI = None
_cryptomap: Dict = {}
+ _backoff: float = 0.0
def __new__(cls):
"""
@@ -47,8 +50,21 @@ class CryptoToFiatConverter:
def _load_cryptomap(self) -> None:
try:
coinlistings = self._coingekko.get_coins_list()
- # Create mapping table from synbol to coingekko_id
+ # Create mapping table from symbol to coingekko_id
self._cryptomap = {x['symbol']: x['id'] for x in coinlistings}
+ except RequestException as request_exception:
+ if "429" in str(request_exception):
+ logger.warning(
+ "Too many requests for Coingecko API, backing off and trying again later.")
+ # Set backoff timestamp to 60 seconds in the future
+ self._backoff = datetime.datetime.now().timestamp() + 60
+ return
+ # If the request is not a 429 error we want to raise the normal error
+ logger.error(
+ "Could not load FIAT Cryptocurrency map for the following problem: {}".format(
+ request_exception
+ )
+ )
except (Exception) as exception:
logger.error(
f"Could not load FIAT Cryptocurrency map for the following problem: {exception}")
@@ -127,6 +143,15 @@ class CryptoToFiatConverter:
if crypto_symbol == fiat_symbol:
return 1.0
+ if self._cryptomap == {}:
+ if self._backoff <= datetime.datetime.now().timestamp():
+ self._load_cryptomap()
+ # return 0.0 if we still dont have data to check, no reason to proceed
+ if self._cryptomap == {}:
+ return 0.0
+ else:
+ return 0.0
+
if crypto_symbol not in self._cryptomap:
# return 0 for unsupported stake currencies (fiat-convert should not break the bot)
logger.warning("unsupported crypto-symbol %s - returning 0.0", crypto_symbol)
diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py
index fd97ad7d4..3f26619a9 100644
--- a/freqtrade/rpc/rpc.py
+++ b/freqtrade/rpc/rpc.py
@@ -178,7 +178,7 @@ class RPC:
current_rate = trade.close_rate
current_profit = trade.calc_profit_ratio(current_rate)
current_profit_abs = trade.calc_profit(current_rate)
-
+ current_profit_fiat: Optional[float] = None
# Calculate fiat profit
if self._fiat_converter:
current_profit_fiat = self._fiat_converter.convert_amount(
@@ -220,12 +220,13 @@ class RPC:
return results
def _rpc_status_table(self, stake_currency: str,
- fiat_display_currency: str) -> Tuple[List, List]:
+ fiat_display_currency: str) -> Tuple[List, List, float]:
trades = Trade.get_open_trades()
if not trades:
raise RPCException('no active trade')
else:
trades_list = []
+ fiat_profit_sum = NAN
for trade in trades:
# calculate profit and send message to user
try:
@@ -243,6 +244,8 @@ class RPC:
)
if fiat_profit and not isnan(fiat_profit):
profit_str += f" ({fiat_profit:.2f})"
+ fiat_profit_sum = fiat_profit if isnan(fiat_profit_sum) \
+ else fiat_profit_sum + fiat_profit
trades_list.append([
trade.id,
trade.pair + ('*' if (trade.open_order_id is not None
@@ -256,7 +259,7 @@ class RPC:
profitcol += " (" + fiat_display_currency + ")"
columns = ['ID', 'Pair', 'Since', profitcol]
- return trades_list, columns
+ return trades_list, columns, fiat_profit_sum
def _rpc_daily_profit(
self, timescale: int,
@@ -845,5 +848,7 @@ class RPC:
df_analyzed, arrow.Arrow.utcnow().datetime)
def _rpc_plot_config(self) -> Dict[str, Any]:
-
+ if (self._freqtrade.strategy.plot_config and
+ 'subplots' not in self._freqtrade.strategy.plot_config):
+ self._freqtrade.strategy.plot_config['subplots'] = {}
return self._freqtrade.strategy.plot_config
diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py
index 3eeedcd12..cca87ad91 100644
--- a/freqtrade/rpc/telegram.py
+++ b/freqtrade/rpc/telegram.py
@@ -8,19 +8,21 @@ import logging
from datetime import timedelta
from html import escape
from itertools import chain
-from typing import Any, Callable, Dict, List, Union
+from math import isnan
+from typing import Any, Callable, Dict, List, Optional, Union, cast
import arrow
from tabulate import tabulate
-from telegram import KeyboardButton, ParseMode, ReplyKeyboardMarkup, Update
+from telegram import (InlineKeyboardButton, InlineKeyboardMarkup, KeyboardButton, ParseMode,
+ ReplyKeyboardMarkup, Update)
from telegram.error import NetworkError, TelegramError
-from telegram.ext import CallbackContext, CommandHandler, Updater
+from telegram.ext import CallbackContext, CallbackQueryHandler, CommandHandler, Updater
from telegram.utils.helpers import escape_markdown
from freqtrade.__init__ import __version__
from freqtrade.constants import DUST_PER_COIN
from freqtrade.exceptions import OperationalException
-from freqtrade.misc import round_coin_value
+from freqtrade.misc import chunks, round_coin_value
from freqtrade.rpc import RPC, RPCException, RPCHandler, RPCMessageType
@@ -87,7 +89,7 @@ class Telegram(RPCHandler):
Validates the keyboard configuration from telegram config
section.
"""
- self._keyboard: List[List[Union[str, KeyboardButton]]] = [
+ self._keyboard: List[List[Union[str, KeyboardButton, InlineKeyboardButton]]] = [
['/daily', '/profit', '/balance'],
['/status', '/status table', '/performance'],
['/count', '/start', '/stop', '/help']
@@ -169,6 +171,11 @@ class Telegram(RPCHandler):
[h.command for h in handles]
)
+ self._current_callback_query_handler: Optional[CallbackQueryHandler] = None
+ self._callback_query_handlers = {
+ 'forcebuy': CallbackQueryHandler(self._forcebuy_inline)
+ }
+
def cleanup(self) -> None:
"""
Stops all running telegram threads.
@@ -226,44 +233,58 @@ class Telegram(RPCHandler):
def send_msg(self, msg: Dict[str, Any]) -> None:
""" Send a message to telegram channel """
- noti = self._config['telegram'].get('notification_settings', {}
- ).get(str(msg['type']), 'on')
+ default_noti = 'on'
+
+ msg_type = msg['type']
+ noti = ''
+ if msg_type == RPCMessageType.SELL:
+ sell_noti = self._config['telegram'] \
+ .get('notification_settings', {}).get(str(msg_type), {})
+ # For backward compatibility sell still be string
+ if isinstance(noti, str):
+ noti = sell_noti
+ else:
+ noti = sell_noti.get(str(msg['sell_reason']), default_noti)
+ else:
+ noti = self._config['telegram'] \
+ .get('notification_settings', {}).get(str(msg_type), default_noti)
+
if noti == 'off':
- logger.info(f"Notification '{msg['type']}' not sent.")
+ logger.info(f"Notification '{msg_type}' not sent.")
# Notification disabled
return
- if msg['type'] == RPCMessageType.BUY:
+ if msg_type == RPCMessageType.BUY:
message = self._format_buy_msg(msg)
- elif msg['type'] in (RPCMessageType.BUY_CANCEL, RPCMessageType.SELL_CANCEL):
- msg['message_side'] = 'buy' if msg['type'] == RPCMessageType.BUY_CANCEL else 'sell'
+ elif msg_type in (RPCMessageType.BUY_CANCEL, RPCMessageType.SELL_CANCEL):
+ msg['message_side'] = 'buy' if msg_type == RPCMessageType.BUY_CANCEL else 'sell'
message = ("\N{WARNING SIGN} *{exchange}:* "
"Cancelling open {message_side} Order for {pair} (#{trade_id}). "
"Reason: {reason}.".format(**msg))
- elif msg['type'] == RPCMessageType.BUY_FILL:
+ elif msg_type == RPCMessageType.BUY_FILL:
message = ("\N{LARGE CIRCLE} *{exchange}:* "
"Buy order for {pair} (#{trade_id}) filled "
"for {open_rate}.".format(**msg))
- elif msg['type'] == RPCMessageType.SELL_FILL:
+ elif msg_type == RPCMessageType.SELL_FILL:
message = ("\N{LARGE CIRCLE} *{exchange}:* "
"Sell order for {pair} (#{trade_id}) filled "
"for {close_rate}.".format(**msg))
- elif msg['type'] == RPCMessageType.SELL:
+ elif msg_type == RPCMessageType.SELL:
message = self._format_sell_msg(msg)
- elif msg['type'] == RPCMessageType.STATUS:
+ elif msg_type == RPCMessageType.STATUS:
message = '*Status:* `{status}`'.format(**msg)
- elif msg['type'] == RPCMessageType.WARNING:
+ elif msg_type == RPCMessageType.WARNING:
message = '\N{WARNING SIGN} *Warning:* `{status}`'.format(**msg)
- elif msg['type'] == RPCMessageType.STARTUP:
+ elif msg_type == RPCMessageType.STARTUP:
message = '{status}'.format(**msg)
else:
- raise NotImplementedError('Unknown message type: {}'.format(msg['type']))
+ raise NotImplementedError('Unknown message type: {}'.format(msg_type))
self._send_msg(message, disable_notification=(noti == 'silent'))
@@ -354,19 +375,31 @@ class Telegram(RPCHandler):
:return: None
"""
try:
- statlist, head = self._rpc._rpc_status_table(
- self._config['stake_currency'], self._config.get('fiat_display_currency', ''))
+ fiat_currency = self._config.get('fiat_display_currency', '')
+ statlist, head, fiat_profit_sum = self._rpc._rpc_status_table(
+ self._config['stake_currency'], fiat_currency)
+ show_total = not isnan(fiat_profit_sum) and len(statlist) > 1
max_trades_per_msg = 50
"""
Calculate the number of messages of 50 trades per message
0.99 is used to make sure that there are no extra (empty) messages
As an example with 50 trades, there will be int(50/50 + 0.99) = 1 message
"""
- for i in range(0, max(int(len(statlist) / max_trades_per_msg + 0.99), 1)):
- message = tabulate(statlist[i * max_trades_per_msg:(i + 1) * max_trades_per_msg],
+ messages_count = max(int(len(statlist) / max_trades_per_msg + 0.99), 1)
+ for i in range(0, messages_count):
+ trades = statlist[i * max_trades_per_msg:(i + 1) * max_trades_per_msg]
+ if show_total and i == messages_count - 1:
+ # append total line
+ trades.append(["Total", "", "", f"{fiat_profit_sum:.2f} {fiat_currency}"])
+
+ message = tabulate(trades,
headers=head,
tablefmt='simple')
+ if show_total and i == messages_count - 1:
+ # insert separators line between Total
+ lines = message.split("\n")
+ message = "\n".join(lines[:-1] + [lines[1]] + [lines[-1]])
self._send_msg(f"{message}
", parse_mode=ParseMode.HTML)
except RPCException as e:
self._send_msg(str(e))
@@ -624,6 +657,25 @@ class Telegram(RPCHandler):
except RPCException as e:
self._send_msg(str(e))
+ def _forcebuy_action(self, pair, price=None):
+ try:
+ self._rpc._rpc_forcebuy(pair, price)
+ except RPCException as e:
+ self._send_msg(str(e))
+
+ def _forcebuy_inline(self, update: Update, _: CallbackContext) -> None:
+ if update.callback_query:
+ query = update.callback_query
+ pair = query.data
+ query.answer()
+ query.edit_message_text(text=f"Force Buying: {pair}")
+ self._forcebuy_action(pair)
+
+ @staticmethod
+ def _layout_inline_keyboard(buttons: List[InlineKeyboardButton],
+ cols=3) -> List[List[InlineKeyboardButton]]:
+ return [buttons[i:i + cols] for i in range(0, len(buttons), cols)]
+
@authorized_only
def _forcebuy(self, update: Update, context: CallbackContext) -> None:
"""
@@ -636,10 +688,13 @@ class Telegram(RPCHandler):
if context.args:
pair = context.args[0]
price = float(context.args[1]) if len(context.args) > 1 else None
- try:
- self._rpc._rpc_forcebuy(pair, price)
- except RPCException as e:
- self._send_msg(str(e))
+ self._forcebuy_action(pair, price)
+ else:
+ whitelist = self._rpc._rpc_whitelist()['whitelist']
+ pairs = [InlineKeyboardButton(pair, callback_data=pair) for pair in whitelist]
+ self._send_inline_msg("Which pair?",
+ keyboard=self._layout_inline_keyboard(pairs),
+ callback_query_handler='forcebuy')
@authorized_only
def _trades(self, update: Update, context: CallbackContext) -> None:
@@ -711,8 +766,11 @@ class Telegram(RPCHandler):
trades = self._rpc._rpc_performance()
output = "Performance:\n"
for i, trade in enumerate(trades):
- stat_line = (f"{i+1}.\t {trade['pair']}\t{trade['profit']:.2f}% "
- f"({trade['count']})
\n")
+ stat_line = (
+ f"{i+1}.\t {trade['pair']}\t"
+ f"{round_coin_value(trade['profit_abs'], self._config['stake_currency'])} "
+ f"({trade['profit']:.2f}%) "
+ f"({trade['count']})
\n")
if len(output + stat_line) >= MAX_TELEGRAM_MESSAGE_LENGTH:
self._send_msg(output, parse_mode=ParseMode.HTML)
@@ -750,17 +808,21 @@ class Telegram(RPCHandler):
Handler for /locks.
Returns the currently active locks
"""
- locks = self._rpc._rpc_locks()
- message = tabulate([[
- lock['id'],
- lock['pair'],
- lock['lock_end_time'],
- lock['reason']] for lock in locks['locks']],
- headers=['ID', 'Pair', 'Until', 'Reason'],
- tablefmt='simple')
- message = f"{escape(message)}
"
- logger.debug(message)
- self._send_msg(message, parse_mode=ParseMode.HTML)
+ rpc_locks = self._rpc._rpc_locks()
+ if not rpc_locks['locks']:
+ self._send_msg('No active locks.', parse_mode=ParseMode.HTML)
+
+ for locks in chunks(rpc_locks['locks'], 25):
+ message = tabulate([[
+ lock['id'],
+ lock['pair'],
+ lock['lock_end_time'],
+ lock['reason']] for lock in locks],
+ headers=['ID', 'Pair', 'Until', 'Reason'],
+ tablefmt='simple')
+ message = f"{escape(message)}
"
+ logger.debug(message)
+ self._send_msg(message, parse_mode=ParseMode.HTML)
@authorized_only
def _delete_locks(self, update: Update, context: CallbackContext) -> None:
@@ -860,9 +922,17 @@ class Telegram(RPCHandler):
"""
try:
edge_pairs = self._rpc._rpc_edge()
- edge_pairs_tab = tabulate(edge_pairs, headers='keys', tablefmt='simple')
- message = f'Edge only validated following pairs:\n{edge_pairs_tab}
'
- self._send_msg(message, parse_mode=ParseMode.HTML)
+ if not edge_pairs:
+ message = 'Edge only validated following pairs:'
+ self._send_msg(message, parse_mode=ParseMode.HTML)
+
+ for chunk in chunks(edge_pairs, 25):
+ edge_pairs_tab = tabulate(chunk, headers='keys', tablefmt='simple')
+ message = (f'Edge only validated following pairs:\n'
+ f'{edge_pairs_tab}
')
+
+ self._send_msg(message, parse_mode=ParseMode.HTML)
+
except RPCException as e:
self._send_msg(str(e))
@@ -959,8 +1029,9 @@ class Telegram(RPCHandler):
f"*Current state:* `{val['state']}`"
)
- def _send_msg(self, msg: str, parse_mode: str = ParseMode.MARKDOWN,
- disable_notification: bool = False) -> None:
+ def _send_inline_msg(self, msg: str, callback_query_handler,
+ parse_mode: str = ParseMode.MARKDOWN, disable_notification: bool = False,
+ keyboard: List[List[InlineKeyboardButton]] = None, ) -> None:
"""
Send given markdown message
:param msg: message
@@ -968,7 +1039,29 @@ class Telegram(RPCHandler):
:param parse_mode: telegram parse mode
:return: None
"""
- reply_markup = ReplyKeyboardMarkup(self._keyboard, resize_keyboard=True)
+ if self._current_callback_query_handler:
+ self._updater.dispatcher.remove_handler(self._current_callback_query_handler)
+ self._current_callback_query_handler = self._callback_query_handlers[callback_query_handler]
+ self._updater.dispatcher.add_handler(self._current_callback_query_handler)
+
+ self._send_msg(msg, parse_mode, disable_notification,
+ cast(List[List[Union[str, KeyboardButton, InlineKeyboardButton]]], keyboard),
+ reply_markup=InlineKeyboardMarkup)
+
+ def _send_msg(self, msg: str, parse_mode: str = ParseMode.MARKDOWN,
+ disable_notification: bool = False,
+ keyboard: List[List[Union[str, KeyboardButton, InlineKeyboardButton]]] = None,
+ reply_markup=ReplyKeyboardMarkup) -> None:
+ """
+ Send given markdown message
+ :param msg: message
+ :param bot: alternative bot
+ :param parse_mode: telegram parse mode
+ :return: None
+ """
+ if keyboard is None:
+ keyboard = self._keyboard
+ reply_markup = reply_markup(keyboard, resize_keyboard=True)
try:
try:
self._updater.bot.send_message(
diff --git a/freqtrade/strategy/hyper.py b/freqtrade/strategy/hyper.py
index 32486136d..7dee47d87 100644
--- a/freqtrade/strategy/hyper.py
+++ b/freqtrade/strategy/hyper.py
@@ -5,7 +5,9 @@ This module defines a base class for auto-hyperoptable strategies.
import logging
from abc import ABC, abstractmethod
from contextlib import suppress
-from typing import Any, Dict, Iterator, Optional, Sequence, Tuple, Union
+from typing import Any, Dict, Iterator, List, Optional, Sequence, Tuple, Union
+
+from freqtrade.optimize.hyperopt_tools import HyperoptTools
with suppress(ImportError):
@@ -26,7 +28,8 @@ class BaseParameter(ABC):
category: Optional[str]
default: Any
value: Any
- hyperopt: bool = False
+ in_space: bool = False
+ name: str
def __init__(self, *, default: Any, space: Optional[str] = None,
optimize: bool = True, load: bool = True, **kwargs):
@@ -131,7 +134,7 @@ class IntParameter(NumericParameter):
Returns a List with 1 item (`value`) in "non-hyperopt" mode, to avoid
calculating 100ds of indicators.
"""
- if self.hyperopt:
+ if self.in_space and self.optimize:
# Scikit-optimize ranges are "inclusive", while python's "range" is exclusive
return range(self.low, self.high + 1)
else:
@@ -247,6 +250,10 @@ class HyperStrategyMixin(object):
"""
Initialize hyperoptable strategy mixin.
"""
+ self.config = config
+ self.ft_buy_params: List[BaseParameter] = []
+ self.ft_sell_params: List[BaseParameter] = []
+
self._load_hyper_params(config.get('runmode') == RunMode.HYPEROPT)
def enumerate_parameters(self, category: str = None) -> Iterator[Tuple[str, BaseParameter]]:
@@ -257,15 +264,26 @@ class HyperStrategyMixin(object):
"""
if category not in ('buy', 'sell', None):
raise OperationalException('Category must be one of: "buy", "sell", None.')
+
+ if category is None:
+ params = self.ft_buy_params + self.ft_sell_params
+ else:
+ params = getattr(self, f"ft_{category}_params")
+
+ for par in params:
+ yield par.name, par
+
+ def _detect_parameters(self, category: str) -> Iterator[Tuple[str, BaseParameter]]:
+ """ Detect all parameters for 'category' """
for attr_name in dir(self):
if not attr_name.startswith('__'): # Ignore internals, not strictly necessary.
attr = getattr(self, attr_name)
if issubclass(attr.__class__, BaseParameter):
- if (category and attr_name.startswith(category + '_')
+ if (attr_name.startswith(category + '_')
and attr.category is not None and attr.category != category):
raise OperationalException(
f'Inconclusive parameter name {attr_name}, category: {attr.category}.')
- if (category is None or category == attr.category or
+ if (category == attr.category or
(attr_name.startswith(category + '_') and attr.category is None)):
yield attr_name, attr
@@ -283,9 +301,16 @@ class HyperStrategyMixin(object):
"""
if not params:
logger.info(f"No params for {space} found, using default values.")
+ param_container: List[BaseParameter] = getattr(self, f"ft_{space}_params")
+
+ for attr_name, attr in self._detect_parameters(space):
+ attr.name = attr_name
+ attr.in_space = hyperopt and HyperoptTools.has_space(self.config, space)
+ if not attr.category:
+ attr.category = space
+
+ param_container.append(attr)
- for attr_name, attr in self.enumerate_parameters():
- attr.hyperopt = hyperopt
if params and attr_name in params:
if attr.load:
attr.value = params[attr_name]
@@ -295,3 +320,16 @@ class HyperStrategyMixin(object):
f'Default value "{attr.value}" used.')
else:
logger.info(f'Strategy Parameter(default): {attr_name} = {attr.value}')
+
+ def get_params_dict(self):
+ """
+ Returns list of Parameters that are not part of the current optimize job
+ """
+ params = {
+ 'buy': {},
+ 'sell': {}
+ }
+ for name, p in self.enumerate_parameters():
+ if not p.optimize or not p.in_space:
+ params[p.category][name] = p.value
+ return params
diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py
index 645e70e8a..e2cde52eb 100644
--- a/freqtrade/strategy/interface.py
+++ b/freqtrade/strategy/interface.py
@@ -161,6 +161,7 @@ class IStrategy(ABC, HyperStrategyMixin):
:param metadata: Additional information, like the currently traded pair
:return: a Dataframe with all mandatory indicators for the strategies
"""
+ return dataframe
@abstractmethod
def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
@@ -170,6 +171,7 @@ class IStrategy(ABC, HyperStrategyMixin):
:param metadata: Additional information, like the currently traded pair
:return: DataFrame with buy column
"""
+ return dataframe
@abstractmethod
def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
@@ -179,6 +181,7 @@ class IStrategy(ABC, HyperStrategyMixin):
:param metadata: Additional information, like the currently traded pair
:return: DataFrame with sell column
"""
+ return dataframe
def check_buy_timeout(self, pair: str, trade: Trade, order: dict, **kwargs) -> bool:
"""
@@ -226,7 +229,7 @@ class IStrategy(ABC, HyperStrategyMixin):
pass
def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float,
- time_in_force: str, **kwargs) -> bool:
+ time_in_force: str, current_time: datetime, **kwargs) -> bool:
"""
Called right before placing a buy order.
Timing for this function is critical, so avoid doing heavy computations or
@@ -241,6 +244,7 @@ class IStrategy(ABC, HyperStrategyMixin):
:param amount: Amount in target (quote) currency that's going to be traded.
:param rate: Rate that's going to be used when using limit orders
:param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled).
+ :param current_time: datetime object, containing the current datetime
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
:return bool: When True is returned, then the buy-order is placed on the exchange.
False aborts the process
@@ -248,7 +252,8 @@ class IStrategy(ABC, HyperStrategyMixin):
return True
def confirm_trade_exit(self, pair: str, trade: Trade, order_type: str, amount: float,
- rate: float, time_in_force: str, sell_reason: str, **kwargs) -> bool:
+ rate: float, time_in_force: str, sell_reason: str,
+ current_time: datetime, **kwargs) -> bool:
"""
Called right before placing a regular sell order.
Timing for this function is critical, so avoid doing heavy computations or
@@ -267,6 +272,7 @@ class IStrategy(ABC, HyperStrategyMixin):
:param sell_reason: Sell reason.
Can be any of ['roi', 'stop_loss', 'stoploss_on_exchange', 'trailing_stop_loss',
'sell_signal', 'force_sell', 'emergency_sell']
+ :param current_time: datetime object, containing the current datetime
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
:return bool: When True is returned, then the sell-order is placed on the exchange.
False aborts the process
@@ -274,7 +280,7 @@ class IStrategy(ABC, HyperStrategyMixin):
return True
def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime, current_rate: float,
- current_profit: float, dataframe: DataFrame, **kwargs) -> float:
+ current_profit: float, **kwargs) -> float:
"""
Custom stoploss logic, returning the new distance relative to current_rate (as ratio).
e.g. returning -0.05 would create a stoploss 5% below current_rate.
@@ -290,15 +296,13 @@ class IStrategy(ABC, HyperStrategyMixin):
:param current_time: datetime object, containing the current datetime
:param current_rate: Rate, calculated based on pricing settings in ask_strategy.
:param current_profit: Current profit (as ratio), calculated based on current_rate.
- :param dataframe: Analyzed dataframe for this pair. Can contain future data in backtesting.
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
:return float: New stoploss value, relative to the currentrate
"""
return self.stoploss
def custom_sell(self, pair: str, trade: Trade, current_time: datetime, current_rate: float,
- current_profit: float, dataframe: DataFrame,
- **kwargs) -> Optional[Union[str, bool]]:
+ current_profit: float, **kwargs) -> Optional[Union[str, bool]]:
"""
Custom sell signal logic indicating that specified position should be sold. Returning a
string or True from this method is equal to setting sell signal on a candle at specified
@@ -536,8 +540,8 @@ class IStrategy(ABC, HyperStrategyMixin):
else:
return False
- def should_sell(self, dataframe: DataFrame, trade: Trade, rate: float, date: datetime,
- buy: bool, sell: bool, low: float = None, high: float = None,
+ def should_sell(self, trade: Trade, rate: float, date: datetime, buy: bool,
+ sell: bool, low: float = None, high: float = None,
force_stoploss: float = 0) -> SellCheckTuple:
"""
This function evaluates if one of the conditions required to trigger a sell
@@ -553,9 +557,8 @@ class IStrategy(ABC, HyperStrategyMixin):
trade.adjust_min_max_rates(high or current_rate)
- stoplossflag = self.stop_loss_reached(dataframe=dataframe, current_rate=current_rate,
- trade=trade, current_time=date,
- current_profit=current_profit,
+ stoplossflag = self.stop_loss_reached(current_rate=current_rate, trade=trade,
+ current_time=date, current_profit=current_profit,
force_stoploss=force_stoploss, high=high)
# Set current rate to high for backtesting sell
@@ -570,6 +573,10 @@ class IStrategy(ABC, HyperStrategyMixin):
sell_signal = SellType.NONE
custom_reason = ''
+ # use provided rate in backtesting, not high/low.
+ current_rate = rate
+ current_profit = trade.calc_profit_ratio(current_rate)
+
if (ask_strategy.get('sell_profit_only', False)
and current_profit <= ask_strategy.get('sell_profit_offset', 0)):
# sell_profit_only and profit doesn't reach the offset - ignore sell signal
@@ -580,7 +587,7 @@ class IStrategy(ABC, HyperStrategyMixin):
else:
custom_reason = strategy_safe_wrapper(self.custom_sell, default_retval=False)(
pair=trade.pair, trade=trade, current_time=date, current_rate=current_rate,
- current_profit=current_profit, dataframe=dataframe)
+ current_profit=current_profit)
if custom_reason:
sell_signal = SellType.CUSTOM_SELL
if isinstance(custom_reason, str):
@@ -617,7 +624,7 @@ class IStrategy(ABC, HyperStrategyMixin):
# logger.debug(f"{trade.pair} - No sell signal.")
return SellCheckTuple(sell_type=SellType.NONE)
- def stop_loss_reached(self, dataframe: DataFrame, current_rate: float, trade: Trade,
+ def stop_loss_reached(self, current_rate: float, trade: Trade,
current_time: datetime, current_profit: float,
force_stoploss: float, high: float = None) -> SellCheckTuple:
"""
@@ -635,8 +642,7 @@ class IStrategy(ABC, HyperStrategyMixin):
)(pair=trade.pair, trade=trade,
current_time=current_time,
current_rate=current_rate,
- current_profit=current_profit,
- dataframe=dataframe)
+ current_profit=current_profit)
# Sanity check - error cases will return None
if stop_loss_value:
# logger.info(f"{trade.pair} {stop_loss_value=} {current_profit=}")
diff --git a/freqtrade/templates/base_config.json.j2 b/freqtrade/templates/base_config.json.j2
index 42f088f9f..8933ebc6a 100644
--- a/freqtrade/templates/base_config.json.j2
+++ b/freqtrade/templates/base_config.json.j2
@@ -9,7 +9,8 @@
"cancel_open_orders_on_exit": false,
"unfilledtimeout": {
"buy": 10,
- "sell": 30
+ "sell": 30,
+ "unit": "minutes"
},
"bid_strategy": {
"price_side": "bid",
diff --git a/freqtrade/templates/subtemplates/strategy_methods_advanced.j2 b/freqtrade/templates/subtemplates/strategy_methods_advanced.j2
index c69b52cad..2a9ac0690 100644
--- a/freqtrade/templates/subtemplates/strategy_methods_advanced.j2
+++ b/freqtrade/templates/subtemplates/strategy_methods_advanced.j2
@@ -39,7 +39,7 @@ def custom_stoploss(self, pair: str, trade: 'Trade', current_time: 'datetime',
return self.stoploss
def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float,
- time_in_force: str, **kwargs) -> bool:
+ time_in_force: str, current_time: 'datetime', **kwargs) -> bool:
"""
Called right before placing a buy order.
Timing for this function is critical, so avoid doing heavy computations or
@@ -54,6 +54,7 @@ def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: f
:param amount: Amount in target (quote) currency that's going to be traded.
:param rate: Rate that's going to be used when using limit orders
:param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled).
+ :param current_time: datetime object, containing the current datetime
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
:return bool: When True is returned, then the buy-order is placed on the exchange.
False aborts the process
@@ -61,7 +62,8 @@ def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: f
return True
def confirm_trade_exit(self, pair: str, trade: 'Trade', order_type: str, amount: float,
- rate: float, time_in_force: str, sell_reason: str, **kwargs) -> bool:
+ rate: float, time_in_force: str, sell_reason: str,
+ current_time: 'datetime', **kwargs) -> bool:
"""
Called right before placing a regular sell order.
Timing for this function is critical, so avoid doing heavy computations or
@@ -80,6 +82,7 @@ def confirm_trade_exit(self, pair: str, trade: 'Trade', order_type: str, amount:
:param sell_reason: Sell reason.
Can be any of ['roi', 'stop_loss', 'stoploss_on_exchange', 'trailing_stop_loss',
'sell_signal', 'force_sell', 'emergency_sell']
+ :param current_time: datetime object, containing the current datetime
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
:return bool: When True is returned, then the sell-order is placed on the exchange.
False aborts the process
diff --git a/requirements-dev.txt b/requirements-dev.txt
index adf5a81c3..4fbf21260 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -4,14 +4,14 @@
-r requirements-hyperopt.txt
coveralls==3.0.1
-flake8==3.9.1
+flake8==3.9.2
flake8-type-annotations==0.1.0
-flake8-tidy-imports==4.2.1
+flake8-tidy-imports==4.3.0
mypy==0.812
-pytest==6.2.3
+pytest==6.2.4
pytest-asyncio==0.15.1
-pytest-cov==2.11.1
-pytest-mock==3.6.0
+pytest-cov==2.12.0
+pytest-mock==3.6.1
pytest-random-order==1.0.4
isort==5.8.0
diff --git a/requirements-hyperopt.txt b/requirements-hyperopt.txt
index 79ad722e2..5e7e9d9d2 100644
--- a/requirements-hyperopt.txt
+++ b/requirements-hyperopt.txt
@@ -3,7 +3,7 @@
# Required for hyperopt
scipy==1.6.3
-scikit-learn==0.24.1
+scikit-learn==0.24.2
scikit-optimize==0.8.1
filelock==3.0.12
joblib==1.0.1
diff --git a/requirements.txt b/requirements.txt
index 94f1739a1..512b2a588 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,23 +1,23 @@
-numpy==1.20.2
+numpy==1.20.3
pandas==1.2.4
-ccxt==1.48.76
+ccxt==1.50.48
# Pin cryptography for now due to rust build errors with piwheels
cryptography==3.4.7
aiohttp==3.7.4.post0
-SQLAlchemy==1.4.11
-python-telegram-bot==13.4.1
-arrow==1.0.3
-cachetools==4.2.1
+SQLAlchemy==1.4.15
+python-telegram-bot==13.5
+arrow==1.1.0
+cachetools==4.2.2
requests==2.25.1
urllib3==1.26.4
wrapt==1.12.1
jsonschema==3.2.0
-TA-Lib==0.4.19
-technical==1.2.2
+TA-Lib==0.4.20
+technical==1.3.0
tabulate==0.8.9
pycoingecko==2.0.0
-jinja2==2.11.3
+jinja2==3.0.1
tables==3.6.1
blosc==1.10.2
@@ -31,10 +31,10 @@ python-rapidjson==1.0
sdnotify==0.3.2
# API Server
-fastapi==0.63.0
+fastapi==0.65.1
uvicorn==0.13.4
-pyjwt==2.0.1
-aiofiles==0.6.0
+pyjwt==2.1.0
+aiofiles==0.7.0
# Support for colorized terminal output
colorama==0.4.4
diff --git a/scripts/rest_client.py b/scripts/rest_client.py
index 900b784f2..ece0a253e 100755
--- a/scripts/rest_client.py
+++ b/scripts/rest_client.py
@@ -396,7 +396,7 @@ def main(args):
sys.exit()
config = load_config(args['config'])
- url = config.get('api_server', {}).get('server_url', '127.0.0.1')
+ url = config.get('api_server', {}).get('listen_ip_address', '127.0.0.1')
port = config.get('api_server', {}).get('listen_port', '8080')
username = config.get('api_server', {}).get('username')
password = config.get('api_server', {}).get('password')
diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py
index d86bced5d..4d3937d87 100644
--- a/tests/commands/test_commands.py
+++ b/tests/commands/test_commands.py
@@ -918,242 +918,244 @@ def test_start_test_pairlist(mocker, caplog, tickers, default_conf, capsys):
captured.out)
-def test_hyperopt_list(mocker, capsys, caplog, hyperopt_results):
+def test_hyperopt_list(mocker, capsys, caplog, saved_hyperopt_results,
+ saved_hyperopt_results_legacy):
+ for _ in (saved_hyperopt_results, saved_hyperopt_results_legacy):
+ mocker.patch(
+ 'freqtrade.optimize.hyperopt_tools.HyperoptTools.load_previous_results',
+ MagicMock(return_value=saved_hyperopt_results_legacy)
+ )
+
+ args = [
+ "hyperopt-list",
+ "--no-details",
+ "--no-color",
+ ]
+ pargs = get_args(args)
+ pargs['config'] = None
+ start_hyperopt_list(pargs)
+ captured = capsys.readouterr()
+ assert all(x in captured.out
+ for x in [" 1/12", " 2/12", " 3/12", " 4/12", " 5/12",
+ " 6/12", " 7/12", " 8/12", " 9/12", " 10/12",
+ " 11/12", " 12/12"])
+ args = [
+ "hyperopt-list",
+ "--best",
+ "--no-details",
+ "--no-color",
+ ]
+ pargs = get_args(args)
+ pargs['config'] = None
+ start_hyperopt_list(pargs)
+ captured = capsys.readouterr()
+ assert all(x in captured.out
+ for x in [" 1/12", " 5/12", " 10/12"])
+ assert all(x not in captured.out
+ for x in [" 2/12", " 3/12", " 4/12", " 6/12", " 7/12", " 8/12", " 9/12",
+ " 11/12", " 12/12"])
+ args = [
+ "hyperopt-list",
+ "--profitable",
+ "--no-details",
+ "--no-color",
+ ]
+ pargs = get_args(args)
+ pargs['config'] = None
+ start_hyperopt_list(pargs)
+ captured = capsys.readouterr()
+ assert all(x in captured.out
+ for x in [" 2/12", " 10/12"])
+ assert all(x not in captured.out
+ for x in [" 1/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12", " 8/12", " 9/12",
+ " 11/12", " 12/12"])
+ args = [
+ "hyperopt-list",
+ "--profitable",
+ "--no-color",
+ ]
+ pargs = get_args(args)
+ pargs['config'] = None
+ start_hyperopt_list(pargs)
+ captured = capsys.readouterr()
+ assert all(x in captured.out
+ for x in [" 2/12", " 10/12", "Best result:", "Buy hyperspace params",
+ "Sell hyperspace params", "ROI table", "Stoploss"])
+ assert all(x not in captured.out
+ for x in [" 1/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12", " 8/12", " 9/12",
+ " 11/12", " 12/12"])
+ args = [
+ "hyperopt-list",
+ "--no-details",
+ "--no-color",
+ "--min-trades", "20",
+ ]
+ pargs = get_args(args)
+ pargs['config'] = None
+ start_hyperopt_list(pargs)
+ captured = capsys.readouterr()
+ assert all(x in captured.out
+ for x in [" 3/12", " 6/12", " 7/12", " 9/12", " 11/12"])
+ assert all(x not in captured.out
+ for x in [" 1/12", " 2/12", " 4/12", " 5/12", " 8/12", " 10/12", " 12/12"])
+ args = [
+ "hyperopt-list",
+ "--profitable",
+ "--no-details",
+ "--no-color",
+ "--max-trades", "20",
+ ]
+ pargs = get_args(args)
+ pargs['config'] = None
+ start_hyperopt_list(pargs)
+ captured = capsys.readouterr()
+ assert all(x in captured.out
+ for x in [" 2/12", " 10/12"])
+ assert all(x not in captured.out
+ for x in [" 1/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12", " 8/12", " 9/12",
+ " 11/12", " 12/12"])
+ args = [
+ "hyperopt-list",
+ "--profitable",
+ "--no-details",
+ "--no-color",
+ "--min-avg-profit", "0.11",
+ ]
+ pargs = get_args(args)
+ pargs['config'] = None
+ start_hyperopt_list(pargs)
+ captured = capsys.readouterr()
+ assert all(x in captured.out
+ for x in [" 2/12"])
+ assert all(x not in captured.out
+ for x in [" 1/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12", " 8/12", " 9/12",
+ " 10/12", " 11/12", " 12/12"])
+ args = [
+ "hyperopt-list",
+ "--no-details",
+ "--no-color",
+ "--max-avg-profit", "0.10",
+ ]
+ pargs = get_args(args)
+ pargs['config'] = None
+ start_hyperopt_list(pargs)
+ captured = capsys.readouterr()
+ assert all(x in captured.out
+ for x in [" 1/12", " 3/12", " 5/12", " 6/12", " 7/12", " 8/12", " 9/12",
+ " 11/12"])
+ assert all(x not in captured.out
+ for x in [" 2/12", " 4/12", " 10/12", " 12/12"])
+ args = [
+ "hyperopt-list",
+ "--no-details",
+ "--no-color",
+ "--min-total-profit", "0.4",
+ ]
+ pargs = get_args(args)
+ pargs['config'] = None
+ start_hyperopt_list(pargs)
+ captured = capsys.readouterr()
+ assert all(x in captured.out
+ for x in [" 10/12"])
+ assert all(x not in captured.out
+ for x in [" 1/12", " 2/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12", " 8/12",
+ " 9/12", " 11/12", " 12/12"])
+ args = [
+ "hyperopt-list",
+ "--no-details",
+ "--no-color",
+ "--max-total-profit", "0.4",
+ ]
+ pargs = get_args(args)
+ pargs['config'] = None
+ start_hyperopt_list(pargs)
+ captured = capsys.readouterr()
+ assert all(x in captured.out
+ for x in [" 1/12", " 2/12", " 3/12", " 5/12", " 6/12", " 7/12", " 8/12",
+ " 9/12", " 11/12"])
+ assert all(x not in captured.out
+ for x in [" 4/12", " 10/12", " 12/12"])
+ args = [
+ "hyperopt-list",
+ "--no-details",
+ "--no-color",
+ "--min-objective", "0.1",
+ ]
+ pargs = get_args(args)
+ pargs['config'] = None
+ start_hyperopt_list(pargs)
+ captured = capsys.readouterr()
+ assert all(x in captured.out
+ for x in [" 10/12"])
+ assert all(x not in captured.out
+ for x in [" 1/12", " 2/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12", " 8/12",
+ " 9/12", " 11/12", " 12/12"])
+ args = [
+ "hyperopt-list",
+ "--no-details",
+ "--max-objective", "0.1",
+ ]
+ pargs = get_args(args)
+ pargs['config'] = None
+ start_hyperopt_list(pargs)
+ captured = capsys.readouterr()
+ assert all(x in captured.out
+ for x in [" 1/12", " 2/12", " 3/12", " 5/12", " 6/12", " 7/12", " 8/12",
+ " 9/12", " 11/12"])
+ assert all(x not in captured.out
+ for x in [" 4/12", " 10/12", " 12/12"])
+ args = [
+ "hyperopt-list",
+ "--profitable",
+ "--no-details",
+ "--no-color",
+ "--min-avg-time", "2000",
+ ]
+ pargs = get_args(args)
+ pargs['config'] = None
+ start_hyperopt_list(pargs)
+ captured = capsys.readouterr()
+ assert all(x in captured.out
+ for x in [" 10/12"])
+ assert all(x not in captured.out
+ for x in [" 1/12", " 2/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12",
+ " 8/12", " 9/12", " 11/12", " 12/12"])
+ args = [
+ "hyperopt-list",
+ "--no-details",
+ "--no-color",
+ "--max-avg-time", "1500",
+ ]
+ pargs = get_args(args)
+ pargs['config'] = None
+ start_hyperopt_list(pargs)
+ captured = capsys.readouterr()
+ assert all(x in captured.out
+ for x in [" 2/12", " 6/12"])
+ assert all(x not in captured.out
+ for x in [" 1/12", " 3/12", " 4/12", " 5/12", " 7/12", " 8/12"
+ " 9/12", " 10/12", " 11/12", " 12/12"])
+ args = [
+ "hyperopt-list",
+ "--no-details",
+ "--no-color",
+ "--export-csv", "test_file.csv",
+ ]
+ pargs = get_args(args)
+ pargs['config'] = None
+ start_hyperopt_list(pargs)
+ captured = capsys.readouterr()
+ log_has("CSV file created: test_file.csv", caplog)
+ f = Path("test_file.csv")
+ assert 'Best,1,2,-1.25%,-1.2222,-0.00125625,,-2.51,"3,930.0 m",0.43662' in f.read_text()
+ assert f.is_file()
+ f.unlink()
+
+
+def test_hyperopt_show(mocker, capsys, saved_hyperopt_results):
mocker.patch(
'freqtrade.optimize.hyperopt_tools.HyperoptTools.load_previous_results',
- MagicMock(return_value=hyperopt_results)
- )
-
- args = [
- "hyperopt-list",
- "--no-details",
- "--no-color",
- ]
- pargs = get_args(args)
- pargs['config'] = None
- start_hyperopt_list(pargs)
- captured = capsys.readouterr()
- assert all(x in captured.out
- for x in [" 1/12", " 2/12", " 3/12", " 4/12", " 5/12",
- " 6/12", " 7/12", " 8/12", " 9/12", " 10/12",
- " 11/12", " 12/12"])
- args = [
- "hyperopt-list",
- "--best",
- "--no-details",
- "--no-color",
- ]
- pargs = get_args(args)
- pargs['config'] = None
- start_hyperopt_list(pargs)
- captured = capsys.readouterr()
- assert all(x in captured.out
- for x in [" 1/12", " 5/12", " 10/12"])
- assert all(x not in captured.out
- for x in [" 2/12", " 3/12", " 4/12", " 6/12", " 7/12", " 8/12", " 9/12",
- " 11/12", " 12/12"])
- args = [
- "hyperopt-list",
- "--profitable",
- "--no-details",
- "--no-color",
- ]
- pargs = get_args(args)
- pargs['config'] = None
- start_hyperopt_list(pargs)
- captured = capsys.readouterr()
- assert all(x in captured.out
- for x in [" 2/12", " 10/12"])
- assert all(x not in captured.out
- for x in [" 1/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12", " 8/12", " 9/12",
- " 11/12", " 12/12"])
- args = [
- "hyperopt-list",
- "--profitable",
- "--no-color",
- ]
- pargs = get_args(args)
- pargs['config'] = None
- start_hyperopt_list(pargs)
- captured = capsys.readouterr()
- assert all(x in captured.out
- for x in [" 2/12", " 10/12", "Best result:", "Buy hyperspace params",
- "Sell hyperspace params", "ROI table", "Stoploss"])
- assert all(x not in captured.out
- for x in [" 1/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12", " 8/12", " 9/12",
- " 11/12", " 12/12"])
- args = [
- "hyperopt-list",
- "--no-details",
- "--no-color",
- "--min-trades", "20",
- ]
- pargs = get_args(args)
- pargs['config'] = None
- start_hyperopt_list(pargs)
- captured = capsys.readouterr()
- assert all(x in captured.out
- for x in [" 3/12", " 6/12", " 7/12", " 9/12", " 11/12"])
- assert all(x not in captured.out
- for x in [" 1/12", " 2/12", " 4/12", " 5/12", " 8/12", " 10/12", " 12/12"])
- args = [
- "hyperopt-list",
- "--profitable",
- "--no-details",
- "--no-color",
- "--max-trades", "20",
- ]
- pargs = get_args(args)
- pargs['config'] = None
- start_hyperopt_list(pargs)
- captured = capsys.readouterr()
- assert all(x in captured.out
- for x in [" 2/12", " 10/12"])
- assert all(x not in captured.out
- for x in [" 1/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12", " 8/12", " 9/12",
- " 11/12", " 12/12"])
- args = [
- "hyperopt-list",
- "--profitable",
- "--no-details",
- "--no-color",
- "--min-avg-profit", "0.11",
- ]
- pargs = get_args(args)
- pargs['config'] = None
- start_hyperopt_list(pargs)
- captured = capsys.readouterr()
- assert all(x in captured.out
- for x in [" 2/12"])
- assert all(x not in captured.out
- for x in [" 1/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12", " 8/12", " 9/12",
- " 10/12", " 11/12", " 12/12"])
- args = [
- "hyperopt-list",
- "--no-details",
- "--no-color",
- "--max-avg-profit", "0.10",
- ]
- pargs = get_args(args)
- pargs['config'] = None
- start_hyperopt_list(pargs)
- captured = capsys.readouterr()
- assert all(x in captured.out
- for x in [" 1/12", " 3/12", " 5/12", " 6/12", " 7/12", " 8/12", " 9/12",
- " 11/12"])
- assert all(x not in captured.out
- for x in [" 2/12", " 4/12", " 10/12", " 12/12"])
- args = [
- "hyperopt-list",
- "--no-details",
- "--no-color",
- "--min-total-profit", "0.4",
- ]
- pargs = get_args(args)
- pargs['config'] = None
- start_hyperopt_list(pargs)
- captured = capsys.readouterr()
- assert all(x in captured.out
- for x in [" 10/12"])
- assert all(x not in captured.out
- for x in [" 1/12", " 2/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12", " 8/12",
- " 9/12", " 11/12", " 12/12"])
- args = [
- "hyperopt-list",
- "--no-details",
- "--no-color",
- "--max-total-profit", "0.4",
- ]
- pargs = get_args(args)
- pargs['config'] = None
- start_hyperopt_list(pargs)
- captured = capsys.readouterr()
- assert all(x in captured.out
- for x in [" 1/12", " 2/12", " 3/12", " 5/12", " 6/12", " 7/12", " 8/12",
- " 9/12", " 11/12"])
- assert all(x not in captured.out
- for x in [" 4/12", " 10/12", " 12/12"])
- args = [
- "hyperopt-list",
- "--no-details",
- "--no-color",
- "--min-objective", "0.1",
- ]
- pargs = get_args(args)
- pargs['config'] = None
- start_hyperopt_list(pargs)
- captured = capsys.readouterr()
- assert all(x in captured.out
- for x in [" 10/12"])
- assert all(x not in captured.out
- for x in [" 1/12", " 2/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12", " 8/12",
- " 9/12", " 11/12", " 12/12"])
- args = [
- "hyperopt-list",
- "--no-details",
- "--max-objective", "0.1",
- ]
- pargs = get_args(args)
- pargs['config'] = None
- start_hyperopt_list(pargs)
- captured = capsys.readouterr()
- assert all(x in captured.out
- for x in [" 1/12", " 2/12", " 3/12", " 5/12", " 6/12", " 7/12", " 8/12",
- " 9/12", " 11/12"])
- assert all(x not in captured.out
- for x in [" 4/12", " 10/12", " 12/12"])
- args = [
- "hyperopt-list",
- "--profitable",
- "--no-details",
- "--no-color",
- "--min-avg-time", "2000",
- ]
- pargs = get_args(args)
- pargs['config'] = None
- start_hyperopt_list(pargs)
- captured = capsys.readouterr()
- assert all(x in captured.out
- for x in [" 10/12"])
- assert all(x not in captured.out
- for x in [" 1/12", " 2/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12",
- " 8/12", " 9/12", " 11/12", " 12/12"])
- args = [
- "hyperopt-list",
- "--no-details",
- "--no-color",
- "--max-avg-time", "1500",
- ]
- pargs = get_args(args)
- pargs['config'] = None
- start_hyperopt_list(pargs)
- captured = capsys.readouterr()
- assert all(x in captured.out
- for x in [" 2/12", " 6/12"])
- assert all(x not in captured.out
- for x in [" 1/12", " 3/12", " 4/12", " 5/12", " 7/12", " 8/12"
- " 9/12", " 10/12", " 11/12", " 12/12"])
- args = [
- "hyperopt-list",
- "--no-details",
- "--no-color",
- "--export-csv", "test_file.csv",
- ]
- pargs = get_args(args)
- pargs['config'] = None
- start_hyperopt_list(pargs)
- captured = capsys.readouterr()
- log_has("CSV file created: test_file.csv", caplog)
- f = Path("test_file.csv")
- assert 'Best,1,2,-1.25%,-1.2222,-0.00125625,,-2.51,"3,930.0 m",0.43662' in f.read_text()
- assert f.is_file()
- f.unlink()
-
-
-def test_hyperopt_show(mocker, capsys, hyperopt_results):
- mocker.patch(
- 'freqtrade.optimize.hyperopt_tools.HyperoptTools.load_previous_results',
- MagicMock(return_value=hyperopt_results)
+ MagicMock(return_value=saved_hyperopt_results)
)
args = [
diff --git a/tests/conftest.py b/tests/conftest.py
index 788586134..ef2bd0613 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -3,7 +3,7 @@ import json
import logging
import re
from copy import deepcopy
-from datetime import datetime
+from datetime import datetime, timedelta
from functools import reduce
from pathlib import Path
from unittest.mock import MagicMock, Mock, PropertyMock
@@ -21,6 +21,7 @@ from freqtrade.exchange import Exchange
from freqtrade.freqtradebot import FreqtradeBot
from freqtrade.persistence import LocalTrade, Trade, init_db
from freqtrade.resolvers import ExchangeResolver
+from freqtrade.state import RunMode
from freqtrade.worker import Worker
from tests.conftest_trades import (mock_trade_1, mock_trade_2, mock_trade_3, mock_trade_4,
mock_trade_5, mock_trade_6)
@@ -1677,6 +1678,7 @@ def buy_order_fee():
@pytest.fixture(scope="function")
def edge_conf(default_conf):
conf = deepcopy(default_conf)
+ conf['runmode'] = RunMode.DRY_RUN
conf['max_open_trades'] = -1
conf['tradable_balance_ratio'] = 0.5
conf['stake_amount'] = constants.UNLIMITED_STAKE_AMOUNT
@@ -1778,7 +1780,7 @@ def open_trade():
@pytest.fixture
-def hyperopt_results():
+def saved_hyperopt_results_legacy():
return [
{
'loss': 0.4366182531161519,
@@ -1907,3 +1909,136 @@ def hyperopt_results():
'is_best': False
}
]
+
+
+@pytest.fixture
+def saved_hyperopt_results():
+ return [
+ {
+ 'loss': 0.4366182531161519,
+ 'params_dict': {
+ 'mfi-value': 15, 'fastd-value': 20, 'adx-value': 25, 'rsi-value': 28, 'mfi-enabled': False, 'fastd-enabled': True, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'macd_cross_signal', 'sell-mfi-value': 88, 'sell-fastd-value': 97, 'sell-adx-value': 51, 'sell-rsi-value': 67, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper', 'roi_t1': 1190, 'roi_t2': 541, 'roi_t3': 408, 'roi_p1': 0.026035863879169705, 'roi_p2': 0.12508730043628782, 'roi_p3': 0.27766427921605896, 'stoploss': -0.2562930402099556}, # noqa: E501
+ 'params_details': {'buy': {'mfi-value': 15, 'fastd-value': 20, 'adx-value': 25, 'rsi-value': 28, 'mfi-enabled': False, 'fastd-enabled': True, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'macd_cross_signal'}, 'sell': {'sell-mfi-value': 88, 'sell-fastd-value': 97, 'sell-adx-value': 51, 'sell-rsi-value': 67, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper'}, 'roi': {0: 0.4287874435315165, 408: 0.15112316431545753, 949: 0.026035863879169705, 2139: 0}, 'stoploss': {'stoploss': -0.2562930402099556}}, # noqa: E501
+ 'results_metrics': {'total_trades': 2, 'wins': 0, 'draws': 0, 'losses': 2, 'profit_mean': -0.01254995, 'profit_median': -0.012222, 'profit_total': -0.00125625, 'profit_total_abs': -2.50999, 'holding_avg': timedelta(minutes=3930.0)}, # noqa: E501
+ 'results_explanation': ' 2 trades. Avg profit -1.25%. Total profit -0.00125625 BTC ( -2.51Σ%). Avg duration 3930.0 min.', # noqa: E501
+ 'total_profit': -0.00125625,
+ 'current_epoch': 1,
+ 'is_initial_point': True,
+ 'is_best': True
+ }, {
+ 'loss': 20.0,
+ 'params_dict': {
+ 'mfi-value': 17, 'fastd-value': 38, 'adx-value': 48, 'rsi-value': 22, 'mfi-enabled': True, 'fastd-enabled': False, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'macd_cross_signal', 'sell-mfi-value': 96, 'sell-fastd-value': 68, 'sell-adx-value': 63, 'sell-rsi-value': 81, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-sar_reversal', 'roi_t1': 334, 'roi_t2': 683, 'roi_t3': 140, 'roi_p1': 0.06403981740598495, 'roi_p2': 0.055519840060645045, 'roi_p3': 0.3253712811342459, 'stoploss': -0.338070047333259}, # noqa: E501
+ 'params_details': {
+ 'buy': {'mfi-value': 17, 'fastd-value': 38, 'adx-value': 48, 'rsi-value': 22, 'mfi-enabled': True, 'fastd-enabled': False, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'macd_cross_signal'}, # noqa: E501
+ 'sell': {'sell-mfi-value': 96, 'sell-fastd-value': 68, 'sell-adx-value': 63, 'sell-rsi-value': 81, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-sar_reversal'}, # noqa: E501
+ 'roi': {0: 0.4449309386008759, 140: 0.11955965746663, 823: 0.06403981740598495, 1157: 0}, # noqa: E501
+ 'stoploss': {'stoploss': -0.338070047333259}},
+ 'results_metrics': {'total_trades': 1, 'wins': 0, 'draws': 0, 'losses': 1, 'profit_mean': 0.012357, 'profit_median': -0.012222, 'profit_total': 6.185e-05, 'profit_total_abs': 0.12357, 'holding_avg': timedelta(minutes=1200.0)}, # noqa: E501
+ 'results_explanation': ' 1 trades. Avg profit 0.12%. Total profit 0.00006185 BTC ( 0.12Σ%). Avg duration 1200.0 min.', # noqa: E501
+ 'total_profit': 6.185e-05,
+ 'current_epoch': 2,
+ 'is_initial_point': True,
+ 'is_best': False
+ }, {
+ 'loss': 14.241196856510731,
+ 'params_dict': {'mfi-value': 25, 'fastd-value': 16, 'adx-value': 29, 'rsi-value': 20, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'macd_cross_signal', 'sell-mfi-value': 98, 'sell-fastd-value': 72, 'sell-adx-value': 51, 'sell-rsi-value': 82, 'sell-mfi-enabled': True, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-macd_cross_signal', 'roi_t1': 889, 'roi_t2': 533, 'roi_t3': 263, 'roi_p1': 0.04759065393663096, 'roi_p2': 0.1488819964638463, 'roi_p3': 0.4102801822104605, 'stoploss': -0.05394588767607611}, # noqa: E501
+ 'params_details': {'buy': {'mfi-value': 25, 'fastd-value': 16, 'adx-value': 29, 'rsi-value': 20, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'macd_cross_signal'}, 'sell': {'sell-mfi-value': 98, 'sell-fastd-value': 72, 'sell-adx-value': 51, 'sell-rsi-value': 82, 'sell-mfi-enabled': True, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-macd_cross_signal'}, 'roi': {0: 0.6067528326109377, 263: 0.19647265040047726, 796: 0.04759065393663096, 1685: 0}, 'stoploss': {'stoploss': -0.05394588767607611}}, # noqa: E501
+ 'results_metrics': {'total_trades': 621, 'wins': 320, 'draws': 0, 'losses': 301, 'profit_mean': -0.043883302093397747, 'profit_median': -0.012222, 'profit_total': -0.13639474, 'profit_total_abs': -272.515306, 'holding_avg': timedelta(minutes=1691.207729468599)}, # noqa: E501
+ 'results_explanation': ' 621 trades. Avg profit -0.44%. Total profit -0.13639474 BTC (-272.52Σ%). Avg duration 1691.2 min.', # noqa: E501
+ 'total_profit': -0.13639474,
+ 'current_epoch': 3,
+ 'is_initial_point': True,
+ 'is_best': False
+ }, {
+ 'loss': 100000,
+ 'params_dict': {'mfi-value': 13, 'fastd-value': 35, 'adx-value': 39, 'rsi-value': 29, 'mfi-enabled': True, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': True, 'trigger': 'macd_cross_signal', 'sell-mfi-value': 87, 'sell-fastd-value': 54, 'sell-adx-value': 63, 'sell-rsi-value': 93, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper', 'roi_t1': 1402, 'roi_t2': 676, 'roi_t3': 215, 'roi_p1': 0.06264755784937427, 'roi_p2': 0.14258587851894644, 'roi_p3': 0.20671291201040828, 'stoploss': -0.11818343570194478}, # noqa: E501
+ 'params_details': {'buy': {'mfi-value': 13, 'fastd-value': 35, 'adx-value': 39, 'rsi-value': 29, 'mfi-enabled': True, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': True, 'trigger': 'macd_cross_signal'}, 'sell': {'sell-mfi-value': 87, 'sell-fastd-value': 54, 'sell-adx-value': 63, 'sell-rsi-value': 93, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper'}, 'roi': {0: 0.411946348378729, 215: 0.2052334363683207, 891: 0.06264755784937427, 2293: 0}, 'stoploss': {'stoploss': -0.11818343570194478}}, # noqa: E501
+ 'results_metrics': {'total_trades': 0, 'wins': 0, 'draws': 0, 'losses': 0, 'profit_mean': None, 'profit_median': None, 'profit_total': 0, 'profit': 0.0, 'holding_avg': timedelta()}, # noqa: E501
+ 'results_explanation': ' 0 trades. Avg profit nan%. Total profit 0.00000000 BTC ( 0.00Σ%). Avg duration nan min.', # noqa: E501
+ 'total_profit': 0, 'current_epoch': 4, 'is_initial_point': True, 'is_best': False
+ }, {
+ 'loss': 0.22195522184191518,
+ 'params_dict': {'mfi-value': 17, 'fastd-value': 21, 'adx-value': 38, 'rsi-value': 33, 'mfi-enabled': True, 'fastd-enabled': False, 'adx-enabled': True, 'rsi-enabled': False, 'trigger': 'macd_cross_signal', 'sell-mfi-value': 87, 'sell-fastd-value': 82, 'sell-adx-value': 78, 'sell-rsi-value': 69, 'sell-mfi-enabled': True, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': False, 'sell-trigger': 'sell-macd_cross_signal', 'roi_t1': 1269, 'roi_t2': 601, 'roi_t3': 444, 'roi_p1': 0.07280999507931168, 'roi_p2': 0.08946698095898986, 'roi_p3': 0.1454876733325284, 'stoploss': -0.18181041180901014}, # noqa: E501
+ 'params_details': {'buy': {'mfi-value': 17, 'fastd-value': 21, 'adx-value': 38, 'rsi-value': 33, 'mfi-enabled': True, 'fastd-enabled': False, 'adx-enabled': True, 'rsi-enabled': False, 'trigger': 'macd_cross_signal'}, 'sell': {'sell-mfi-value': 87, 'sell-fastd-value': 82, 'sell-adx-value': 78, 'sell-rsi-value': 69, 'sell-mfi-enabled': True, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': False, 'sell-trigger': 'sell-macd_cross_signal'}, 'roi': {0: 0.3077646493708299, 444: 0.16227697603830155, 1045: 0.07280999507931168, 2314: 0}, 'stoploss': {'stoploss': -0.18181041180901014}}, # noqa: E501
+ 'results_metrics': {'total_trades': 14, 'wins': 6, 'draws': 0, 'losses': 8, 'profit_mean': -0.003539515, 'profit_median': -0.012222, 'profit_total': -0.002480140000000001, 'profit_total_abs': -4.955321, 'holding_avg': timedelta(minutes=3402.8571428571427)}, # noqa: E501
+ 'results_explanation': ' 14 trades. Avg profit -0.35%. Total profit -0.00248014 BTC ( -4.96Σ%). Avg duration 3402.9 min.', # noqa: E501
+ 'total_profit': -0.002480140000000001,
+ 'current_epoch': 5,
+ 'is_initial_point': True,
+ 'is_best': True
+ }, {
+ 'loss': 0.545315889154162,
+ 'params_dict': {'mfi-value': 22, 'fastd-value': 43, 'adx-value': 46, 'rsi-value': 20, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'bb_lower', 'sell-mfi-value': 87, 'sell-fastd-value': 65, 'sell-adx-value': 94, 'sell-rsi-value': 63, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-macd_cross_signal', 'roi_t1': 319, 'roi_t2': 556, 'roi_t3': 216, 'roi_p1': 0.06251955472249589, 'roi_p2': 0.11659519602202795, 'roi_p3': 0.0953744132197762, 'stoploss': -0.024551752215582423}, # noqa: E501
+ 'params_details': {'buy': {'mfi-value': 22, 'fastd-value': 43, 'adx-value': 46, 'rsi-value': 20, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'bb_lower'}, 'sell': {'sell-mfi-value': 87, 'sell-fastd-value': 65, 'sell-adx-value': 94, 'sell-rsi-value': 63, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-macd_cross_signal'}, 'roi': {0: 0.2744891639643, 216: 0.17911475074452382, 772: 0.06251955472249589, 1091: 0}, 'stoploss': {'stoploss': -0.024551752215582423}}, # noqa: E501
+ 'results_metrics': {'total_trades': 39, 'wins': 20, 'draws': 0, 'losses': 19, 'profit_mean': -0.0021400679487179478, 'profit_median': -0.012222, 'profit_total': -0.0041773, 'profit_total_abs': -8.346264999999997, 'holding_avg': timedelta(minutes=636.9230769230769)}, # noqa: E501
+ 'results_explanation': ' 39 trades. Avg profit -0.21%. Total profit -0.00417730 BTC ( -8.35Σ%). Avg duration 636.9 min.', # noqa: E501
+ 'total_profit': -0.0041773,
+ 'current_epoch': 6,
+ 'is_initial_point': True,
+ 'is_best': False
+ }, {
+ 'loss': 4.713497421432944,
+ 'params_dict': {'mfi-value': 13, 'fastd-value': 41, 'adx-value': 21, 'rsi-value': 29, 'mfi-enabled': False, 'fastd-enabled': True, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'bb_lower', 'sell-mfi-value': 99, 'sell-fastd-value': 60, 'sell-adx-value': 81, 'sell-rsi-value': 69, 'sell-mfi-enabled': True, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': False, 'sell-trigger': 'sell-macd_cross_signal', 'roi_t1': 771, 'roi_t2': 620, 'roi_t3': 145, 'roi_p1': 0.0586919200378493, 'roi_p2': 0.04984118697312542, 'roi_p3': 0.37521058680247044, 'stoploss': -0.14613268022709905}, # noqa: E501
+ 'params_details': {
+ 'buy': {'mfi-value': 13, 'fastd-value': 41, 'adx-value': 21, 'rsi-value': 29, 'mfi-enabled': False, 'fastd-enabled': True, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'bb_lower'}, 'sell': {'sell-mfi-value': 99, 'sell-fastd-value': 60, 'sell-adx-value': 81, 'sell-rsi-value': 69, 'sell-mfi-enabled': True, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': False, 'sell-trigger': 'sell-macd_cross_signal'}, 'roi': {0: 0.4837436938134452, 145: 0.10853310701097472, 765: 0.0586919200378493, 1536: 0}, # noqa: E501
+ 'stoploss': {'stoploss': -0.14613268022709905}}, # noqa: E501
+ 'results_metrics': {'total_trades': 318, 'wins': 100, 'draws': 0, 'losses': 218, 'profit_mean': -0.0039833954716981146, 'profit_median': -0.012222, 'profit_total': -0.06339929, 'profit_total_abs': -126.67197600000004, 'holding_avg': timedelta(minutes=3140.377358490566)}, # noqa: E501
+ 'results_explanation': ' 318 trades. Avg profit -0.40%. Total profit -0.06339929 BTC (-126.67Σ%). Avg duration 3140.4 min.', # noqa: E501
+ 'total_profit': -0.06339929,
+ 'current_epoch': 7,
+ 'is_initial_point': True,
+ 'is_best': False
+ }, {
+ 'loss': 20.0, # noqa: E501
+ 'params_dict': {'mfi-value': 24, 'fastd-value': 43, 'adx-value': 33, 'rsi-value': 20, 'mfi-enabled': False, 'fastd-enabled': True, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'sar_reversal', 'sell-mfi-value': 89, 'sell-fastd-value': 74, 'sell-adx-value': 70, 'sell-rsi-value': 70, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': False, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-sar_reversal', 'roi_t1': 1149, 'roi_t2': 375, 'roi_t3': 289, 'roi_p1': 0.05571820757172588, 'roi_p2': 0.0606240398618907, 'roi_p3': 0.1729012220156157, 'stoploss': -0.1588514289110401}, # noqa: E501
+ 'params_details': {'buy': {'mfi-value': 24, 'fastd-value': 43, 'adx-value': 33, 'rsi-value': 20, 'mfi-enabled': False, 'fastd-enabled': True, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'sar_reversal'}, 'sell': {'sell-mfi-value': 89, 'sell-fastd-value': 74, 'sell-adx-value': 70, 'sell-rsi-value': 70, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': False, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-sar_reversal'}, 'roi': {0: 0.2892434694492323, 289: 0.11634224743361658, 664: 0.05571820757172588, 1813: 0}, 'stoploss': {'stoploss': -0.1588514289110401}}, # noqa: E501
+ 'results_metrics': {'total_trades': 1, 'wins': 0, 'draws': 1, 'losses': 0, 'profit_mean': 0.0, 'profit_median': 0.0, 'profit_total': 0.0, 'profit_total_abs': 0.0, 'holding_avg': timedelta(minutes=5340.0)}, # noqa: E501
+ 'results_explanation': ' 1 trades. Avg profit 0.00%. Total profit 0.00000000 BTC ( 0.00Σ%). Avg duration 5340.0 min.', # noqa: E501
+ 'total_profit': 0.0,
+ 'current_epoch': 8,
+ 'is_initial_point': True,
+ 'is_best': False
+ }, {
+ 'loss': 2.4731817780991223,
+ 'params_dict': {'mfi-value': 22, 'fastd-value': 20, 'adx-value': 29, 'rsi-value': 40, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'sar_reversal', 'sell-mfi-value': 97, 'sell-fastd-value': 65, 'sell-adx-value': 81, 'sell-rsi-value': 64, 'sell-mfi-enabled': True, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper', 'roi_t1': 1012, 'roi_t2': 584, 'roi_t3': 422, 'roi_p1': 0.036764323603472565, 'roi_p2': 0.10335480573205287, 'roi_p3': 0.10322347377503042, 'stoploss': -0.2780610808108503}, # noqa: E501
+ 'params_details': {'buy': {'mfi-value': 22, 'fastd-value': 20, 'adx-value': 29, 'rsi-value': 40, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'sar_reversal'}, 'sell': {'sell-mfi-value': 97, 'sell-fastd-value': 65, 'sell-adx-value': 81, 'sell-rsi-value': 64, 'sell-mfi-enabled': True, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper'}, 'roi': {0: 0.2433426031105559, 422: 0.14011912933552545, 1006: 0.036764323603472565, 2018: 0}, 'stoploss': {'stoploss': -0.2780610808108503}}, # noqa: E501
+ 'results_metrics': {'total_trades': 229, 'wins': 150, 'draws': 0, 'losses': 79, 'profit_mean': -0.0038433433624454144, 'profit_median': -0.012222, 'profit_total': -0.044050070000000004, 'profit_total_abs': -88.01256299999999, 'holding_avg': timedelta(minutes=6505.676855895196)}, # noqa: E501
+ 'results_explanation': ' 229 trades. Avg profit -0.38%. Total profit -0.04405007 BTC ( -88.01Σ%). Avg duration 6505.7 min.', # noqa: E501
+ 'total_profit': -0.044050070000000004, # noqa: E501
+ 'current_epoch': 9,
+ 'is_initial_point': True,
+ 'is_best': False
+ }, {
+ 'loss': -0.2604606005845212, # noqa: E501
+ 'params_dict': {'mfi-value': 23, 'fastd-value': 24, 'adx-value': 22, 'rsi-value': 24, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': True, 'trigger': 'macd_cross_signal', 'sell-mfi-value': 97, 'sell-fastd-value': 70, 'sell-adx-value': 64, 'sell-rsi-value': 80, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-sar_reversal', 'roi_t1': 792, 'roi_t2': 464, 'roi_t3': 215, 'roi_p1': 0.04594053535385903, 'roi_p2': 0.09623192684243963, 'roi_p3': 0.04428219070850663, 'stoploss': -0.16992287161634415}, # noqa: E501
+ 'params_details': {'buy': {'mfi-value': 23, 'fastd-value': 24, 'adx-value': 22, 'rsi-value': 24, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': True, 'trigger': 'macd_cross_signal'}, 'sell': {'sell-mfi-value': 97, 'sell-fastd-value': 70, 'sell-adx-value': 64, 'sell-rsi-value': 80, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-sar_reversal'}, 'roi': {0: 0.18645465290480528, 215: 0.14217246219629864, 679: 0.04594053535385903, 1471: 0}, 'stoploss': {'stoploss': -0.16992287161634415}}, # noqa: E501
+ 'results_metrics': {'total_trades': 4, 'wins': 0, 'draws': 0, 'losses': 4, 'profit_mean': 0.001080385, 'profit_median': -0.012222, 'profit_total': 0.00021629, 'profit_total_abs': 0.432154, 'holding_avg': timedelta(minutes=2850.0)}, # noqa: E501
+ 'results_explanation': ' 4 trades. Avg profit 0.11%. Total profit 0.00021629 BTC ( 0.43Σ%). Avg duration 2850.0 min.', # noqa: E501
+ 'total_profit': 0.00021629,
+ 'current_epoch': 10,
+ 'is_initial_point': True,
+ 'is_best': True
+ }, {
+ 'loss': 4.876465945994304, # noqa: E501
+ 'params_dict': {'mfi-value': 20, 'fastd-value': 32, 'adx-value': 49, 'rsi-value': 23, 'mfi-enabled': True, 'fastd-enabled': True, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'bb_lower', 'sell-mfi-value': 75, 'sell-fastd-value': 56, 'sell-adx-value': 61, 'sell-rsi-value': 62, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-macd_cross_signal', 'roi_t1': 579, 'roi_t2': 614, 'roi_t3': 273, 'roi_p1': 0.05307643172744114, 'roi_p2': 0.1352282078262871, 'roi_p3': 0.1913307406325751, 'stoploss': -0.25728526022513887}, # noqa: E501
+ 'params_details': {'buy': {'mfi-value': 20, 'fastd-value': 32, 'adx-value': 49, 'rsi-value': 23, 'mfi-enabled': True, 'fastd-enabled': True, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'bb_lower'}, 'sell': {'sell-mfi-value': 75, 'sell-fastd-value': 56, 'sell-adx-value': 61, 'sell-rsi-value': 62, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-macd_cross_signal'}, 'roi': {0: 0.3796353801863034, 273: 0.18830463955372825, 887: 0.05307643172744114, 1466: 0}, 'stoploss': {'stoploss': -0.25728526022513887}}, # noqa: E501
+ # New Hyperopt mode!
+ 'results_metrics': {'total_trades': 117, 'wins': 67, 'draws': 0, 'losses': 50, 'profit_mean': -0.012698609145299145, 'profit_median': -0.012222, 'profit_total': -0.07436117, 'profit_total_abs': -148.573727, 'holding_avg': timedelta(minutes=4282.5641025641025)}, # noqa: E501
+ 'results_explanation': ' 117 trades. Avg profit -1.27%. Total profit -0.07436117 BTC (-148.57Σ%). Avg duration 4282.6 min.', # noqa: E501
+ 'total_profit': -0.07436117,
+ 'current_epoch': 11,
+ 'is_initial_point': True,
+ 'is_best': False
+ }, {
+ 'loss': 100000,
+ 'params_dict': {'mfi-value': 10, 'fastd-value': 36, 'adx-value': 31, 'rsi-value': 22, 'mfi-enabled': True, 'fastd-enabled': True, 'adx-enabled': True, 'rsi-enabled': False, 'trigger': 'sar_reversal', 'sell-mfi-value': 80, 'sell-fastd-value': 71, 'sell-adx-value': 60, 'sell-rsi-value': 85, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper', 'roi_t1': 1156, 'roi_t2': 581, 'roi_t3': 408, 'roi_p1': 0.06860454019988212, 'roi_p2': 0.12473718444931989, 'roi_p3': 0.2896360635226823, 'stoploss': -0.30889015124682806}, # noqa: E501
+ 'params_details': {'buy': {'mfi-value': 10, 'fastd-value': 36, 'adx-value': 31, 'rsi-value': 22, 'mfi-enabled': True, 'fastd-enabled': True, 'adx-enabled': True, 'rsi-enabled': False, 'trigger': 'sar_reversal'}, 'sell': {'sell-mfi-value': 80, 'sell-fastd-value': 71, 'sell-adx-value': 60, 'sell-rsi-value': 85, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper'}, 'roi': {0: 0.4829777881718843, 408: 0.19334172464920202, 989: 0.06860454019988212, 2145: 0}, 'stoploss': {'stoploss': -0.30889015124682806}}, # noqa: E501
+ 'results_metrics': {'total_trades': 0, 'wins': 0, 'draws': 0, 'losses': 0, 'profit_mean': None, 'profit_median': None, 'profit_total': 0, 'profit_total_abs': 0.0, 'holding_avg': timedelta()}, # noqa: E501
+ 'results_explanation': ' 0 trades. Avg profit nan%. Total profit 0.00000000 BTC ( 0.00Σ%). Avg duration nan min.', # noqa: E501
+ 'total_profit': 0,
+ 'current_epoch': 12,
+ 'is_initial_point': True,
+ 'is_best': False
+ }
+ ]
diff --git a/tests/data/test_dataprovider.py b/tests/data/test_dataprovider.py
index 6b33fa7f2..b87258c64 100644
--- a/tests/data/test_dataprovider.py
+++ b/tests/data/test_dataprovider.py
@@ -246,3 +246,46 @@ def test_get_analyzed_dataframe(mocker, default_conf, ohlcv_history):
assert dataframe.empty
assert isinstance(time, datetime)
assert time == datetime(1970, 1, 1, tzinfo=timezone.utc)
+
+ # Test backtest mode
+ default_conf["runmode"] = RunMode.BACKTEST
+ dp._set_dataframe_max_index(1)
+ dataframe, time = dp.get_analyzed_dataframe("XRP/BTC", timeframe)
+
+ assert len(dataframe) == 1
+
+ dp._set_dataframe_max_index(2)
+ dataframe, time = dp.get_analyzed_dataframe("XRP/BTC", timeframe)
+ assert len(dataframe) == 2
+
+ dp._set_dataframe_max_index(3)
+ dataframe, time = dp.get_analyzed_dataframe("XRP/BTC", timeframe)
+ assert len(dataframe) == 3
+
+ dp._set_dataframe_max_index(500)
+ dataframe, time = dp.get_analyzed_dataframe("XRP/BTC", timeframe)
+ assert len(dataframe) == len(ohlcv_history)
+
+
+def test_no_exchange_mode(default_conf):
+ dp = DataProvider(default_conf, None)
+
+ message = "Exchange is not available to DataProvider."
+
+ with pytest.raises(OperationalException, match=message):
+ dp.refresh([()])
+
+ with pytest.raises(OperationalException, match=message):
+ dp.ohlcv('XRP/USDT', '5m')
+
+ with pytest.raises(OperationalException, match=message):
+ dp.market('XRP/USDT')
+
+ with pytest.raises(OperationalException, match=message):
+ dp.ticker('XRP/USDT')
+
+ with pytest.raises(OperationalException, match=message):
+ dp.orderbook('XRP/USDT', 20)
+
+ with pytest.raises(OperationalException, match=message):
+ dp.available_pairs()
diff --git a/tests/edge/test_edge.py b/tests/edge/test_edge.py
index 25e0da5e2..c4620e1c7 100644
--- a/tests/edge/test_edge.py
+++ b/tests/edge/test_edge.py
@@ -344,6 +344,8 @@ def test_edge_process_no_trades(mocker, edge_conf, caplog):
def test_edge_process_no_pairs(mocker, edge_conf, caplog):
edge_conf['exchange']['pair_whitelist'] = []
+ mocker.patch('freqtrade.freqtradebot.validate_config_consistency')
+
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
fee_mock = mocker.patch('freqtrade.exchange.Exchange.get_fee', return_value=0.001)
mocker.patch('freqtrade.edge.edge_positioning.refresh_data')
diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py
index 573f41bda..b6b395802 100644
--- a/tests/exchange/test_exchange.py
+++ b/tests/exchange/test_exchange.py
@@ -2084,6 +2084,46 @@ def test_cancel_stoploss_order(default_conf, mocker, exchange_name):
order_id='_', pair='TKN/BTC')
+@pytest.mark.parametrize("exchange_name", EXCHANGES)
+def test_cancel_stoploss_order_with_result(default_conf, mocker, exchange_name):
+ default_conf['dry_run'] = False
+ mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', return_value={'for': 123})
+ mocker.patch('freqtrade.exchange.Ftx.fetch_stoploss_order', return_value={'for': 123})
+ exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
+
+ mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order',
+ return_value={'fee': {}, 'status': 'canceled', 'amount': 1234})
+ mocker.patch('freqtrade.exchange.Ftx.cancel_stoploss_order',
+ return_value={'fee': {}, 'status': 'canceled', 'amount': 1234})
+ co = exchange.cancel_stoploss_order_with_result(order_id='_', pair='TKN/BTC', amount=555)
+ assert co == {'fee': {}, 'status': 'canceled', 'amount': 1234}
+
+ mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order',
+ return_value='canceled')
+ mocker.patch('freqtrade.exchange.Ftx.cancel_stoploss_order',
+ return_value='canceled')
+ # Fall back to fetch_stoploss_order
+ co = exchange.cancel_stoploss_order_with_result(order_id='_', pair='TKN/BTC', amount=555)
+ assert co == {'for': 123}
+
+ mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order',
+ side_effect=InvalidOrderException(""))
+ mocker.patch('freqtrade.exchange.Ftx.fetch_stoploss_order',
+ side_effect=InvalidOrderException(""))
+
+ co = exchange.cancel_stoploss_order_with_result(order_id='_', pair='TKN/BTC', amount=555)
+ assert co['amount'] == 555
+ assert co == {'fee': {}, 'status': 'canceled', 'amount': 555, 'info': {}}
+
+ with pytest.raises(InvalidOrderException):
+ mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order',
+ side_effect=InvalidOrderException("Did not find order"))
+ mocker.patch('freqtrade.exchange.Ftx.cancel_stoploss_order',
+ side_effect=InvalidOrderException("Did not find order"))
+ exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
+ exchange.cancel_stoploss_order_with_result(order_id='_', pair='TKN/BTC', amount=123)
+
+
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_fetch_order(default_conf, mocker, exchange_name):
default_conf['dry_run'] = True
diff --git a/tests/exchange/test_ftx.py b/tests/exchange/test_ftx.py
index 494d86e56..63d99acdf 100644
--- a/tests/exchange/test_ftx.py
+++ b/tests/exchange/test_ftx.py
@@ -157,3 +157,26 @@ def test_fetch_stoploss_order(default_conf, mocker):
'fetch_stoploss_order', 'fetch_orders',
retries=API_FETCH_ORDER_RETRY_COUNT + 1,
order_id='_', pair='TKN/BTC')
+
+
+def test_get_order_id(mocker, default_conf):
+ exchange = get_patched_exchange(mocker, default_conf, id='ftx')
+ order = {
+ 'type': STOPLOSS_ORDERTYPE,
+ 'price': 1500,
+ 'id': '1111',
+ 'info': {
+ 'orderId': '1234'
+ }
+ }
+ assert exchange.get_order_id_conditional(order) == '1234'
+
+ order = {
+ 'type': 'limit',
+ 'price': 1500,
+ 'id': '1111',
+ 'info': {
+ 'orderId': '1234'
+ }
+ }
+ assert exchange.get_order_id_conditional(order) == '1111'
diff --git a/tests/exchange/test_kraken.py b/tests/exchange/test_kraken.py
index 97f428e2f..ed22cde92 100644
--- a/tests/exchange/test_kraken.py
+++ b/tests/exchange/test_kraken.py
@@ -90,6 +90,7 @@ def test_get_balances_prod(default_conf, mocker):
'3ST': balance_item.copy(),
'4ST': balance_item.copy(),
'EUR': balance_item.copy(),
+ 'timestamp': 123123
})
kraken_open_orders = [{'symbol': '1ST/EUR',
'type': 'limit',
@@ -138,7 +139,7 @@ def test_get_balances_prod(default_conf, mocker):
default_conf['dry_run'] = False
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kraken")
balances = exchange.get_balances()
- assert len(balances) == 5
+ assert len(balances) == 6
assert balances['1ST']['free'] == 9.0
assert balances['1ST']['total'] == 10.0
diff --git a/tests/optimize/test_backtest_detail.py b/tests/optimize/test_backtest_detail.py
index 3655b941d..c35a083ec 100644
--- a/tests/optimize/test_backtest_detail.py
+++ b/tests/optimize/test_backtest_detail.py
@@ -185,7 +185,7 @@ tc11 = BTContainer(data=[
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
[1, 5000, 5050, 4950, 5100, 6172, 0, 0],
[2, 5100, 5251, 5100, 5100, 6172, 0, 0],
- [3, 4850, 5050, 4650, 4750, 6172, 0, 0],
+ [3, 5000, 5150, 4650, 4750, 6172, 0, 0],
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=0.019, trailing_stop=True,
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05,
@@ -440,6 +440,23 @@ tc27 = BTContainer(data=[
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=4)]
)
+# Test 28: trailing_stop should raise so candle 3 causes a stoploss
+# Same case than tc11 - but candle 3 "gaps down" - the stoploss will be above the candle,
+# therefore "open" will be used
+# stop-loss: 10%, ROI: 10% (should not apply), stoploss adjusted candle 2
+tc28 = BTContainer(data=[
+ # D O H L C V B S
+ [0, 5000, 5050, 4950, 5000, 6172, 1, 0],
+ [1, 5000, 5050, 4950, 5100, 6172, 0, 0],
+ [2, 5100, 5251, 5100, 5100, 6172, 0, 0],
+ [3, 4850, 5050, 4650, 4750, 6172, 0, 0],
+ [4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
+ stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.03, trailing_stop=True,
+ trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05,
+ trailing_stop_positive=0.03,
+ trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)]
+)
+
TESTS = [
tc0,
tc1,
@@ -469,6 +486,7 @@ TESTS = [
tc25,
tc26,
tc27,
+ tc28,
]
@@ -493,6 +511,7 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None:
patch_exchange(mocker)
frame = _build_backtest_dataframe(data.data)
backtesting = Backtesting(default_conf)
+ backtesting._set_strategy(backtesting.strategylist[0])
backtesting.strategy.advise_buy = lambda a, m: frame
backtesting.strategy.advise_sell = lambda a, m: frame
caplog.set_level(logging.DEBUG)
@@ -501,13 +520,14 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None:
# Dummy data as we mock the analyze functions
data_processed = {pair: frame.copy()}
min_date, max_date = get_timerange({pair: frame})
- results = backtesting.backtest(
+ result = backtesting.backtest(
processed=data_processed,
start_date=min_date,
end_date=max_date,
max_open_trades=10,
)
+ results = result['results']
assert len(results) == len(data.trades)
assert round(results["profit_ratio"].sum(), 3) == round(data.profit_perc, 3)
diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py
index 00114be5b..632d458ce 100644
--- a/tests/optimize/test_backtesting.py
+++ b/tests/optimize/test_backtesting.py
@@ -83,6 +83,7 @@ def simple_backtest(config, contour, mocker, testdatadir) -> None:
patch_exchange(mocker)
config['timeframe'] = '1m'
backtesting = Backtesting(config)
+ backtesting._set_strategy(backtesting.strategylist[0])
data = load_data_test(contour, testdatadir)
processed = backtesting.strategy.ohlcvdata_to_dataframe(data)
@@ -106,6 +107,7 @@ def _make_backtest_conf(mocker, datadir, conf=None, pair='UNITTEST/BTC'):
data = trim_dictlist(data, -201)
patch_exchange(mocker)
backtesting = Backtesting(conf)
+ backtesting._set_strategy(backtesting.strategylist[0])
processed = backtesting.strategy.ohlcvdata_to_dataframe(data)
min_date, max_date = get_timerange(processed)
return {
@@ -285,6 +287,7 @@ def test_backtesting_init(mocker, default_conf, order_types) -> None:
patch_exchange(mocker)
get_fee = mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.5))
backtesting = Backtesting(default_conf)
+ backtesting._set_strategy(backtesting.strategylist[0])
assert backtesting.config == default_conf
assert backtesting.timeframe == '5m'
assert callable(backtesting.strategy.ohlcvdata_to_dataframe)
@@ -315,11 +318,13 @@ def test_data_with_fee(default_conf, mocker, testdatadir) -> None:
fee_mock = mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.5))
backtesting = Backtesting(default_conf)
+ backtesting._set_strategy(backtesting.strategylist[0])
assert backtesting.fee == 0.1234
assert fee_mock.call_count == 0
default_conf['fee'] = 0.0
backtesting = Backtesting(default_conf)
+ backtesting._set_strategy(backtesting.strategylist[0])
assert backtesting.fee == 0.0
assert fee_mock.call_count == 0
@@ -330,6 +335,7 @@ def test_data_to_dataframe_bt(default_conf, mocker, testdatadir) -> None:
data = history.load_data(testdatadir, '1m', ['UNITTEST/BTC'], timerange=timerange,
fill_up_missing=True)
backtesting = Backtesting(default_conf)
+ backtesting._set_strategy(backtesting.strategylist[0])
processed = backtesting.strategy.ohlcvdata_to_dataframe(data)
assert len(processed['UNITTEST/BTC']) == 102
@@ -361,12 +367,13 @@ def test_backtesting_start(default_conf, mocker, testdatadir, caplog) -> None:
default_conf['timerange'] = '-1510694220'
backtesting = Backtesting(default_conf)
+ backtesting._set_strategy(backtesting.strategylist[0])
backtesting.strategy.bot_loop_start = MagicMock()
backtesting.start()
# check the logs, that will contain the backtest result
exists = [
'Backtesting with data from 2017-11-14 21:17:00 '
- 'up to 2017-11-14 22:59:00 (0 days)..'
+ 'up to 2017-11-14 22:59:00 (0 days).'
]
for line in exists:
assert log_has(line, caplog)
@@ -393,6 +400,7 @@ def test_backtesting_start_no_data(default_conf, mocker, caplog, testdatadir) ->
default_conf['timerange'] = '20180101-20180102'
backtesting = Backtesting(default_conf)
+ backtesting._set_strategy(backtesting.strategylist[0])
with pytest.raises(OperationalException, match='No data found. Terminating.'):
backtesting.start()
@@ -465,6 +473,7 @@ def test_backtest__enter_trade(default_conf, fee, mocker) -> None:
default_conf['stake_amount'] = 'unlimited'
default_conf['max_open_trades'] = 2
backtesting = Backtesting(default_conf)
+ backtesting._set_strategy(backtesting.strategylist[0])
pair = 'UNITTEST/BTC'
row = [
pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=0),
@@ -508,19 +517,21 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None:
mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001)
patch_exchange(mocker)
backtesting = Backtesting(default_conf)
+ backtesting._set_strategy(backtesting.strategylist[0])
pair = 'UNITTEST/BTC'
timerange = TimeRange('date', None, 1517227800, 0)
data = history.load_data(datadir=testdatadir, timeframe='5m', pairs=['UNITTEST/BTC'],
timerange=timerange)
processed = backtesting.strategy.ohlcvdata_to_dataframe(data)
min_date, max_date = get_timerange(processed)
- results = backtesting.backtest(
+ result = backtesting.backtest(
processed=processed,
start_date=min_date,
end_date=max_date,
max_open_trades=10,
position_stacking=False,
)
+ results = result['results']
assert not results.empty
assert len(results) == 2
@@ -569,6 +580,7 @@ def test_backtest_1min_timeframe(default_conf, fee, mocker, testdatadir) -> None
mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001)
patch_exchange(mocker)
backtesting = Backtesting(default_conf)
+ backtesting._set_strategy(backtesting.strategylist[0])
# Run a backtesting for an exiting 1min timeframe
timerange = TimeRange.parse_timerange('1510688220-1510700340')
@@ -583,13 +595,14 @@ def test_backtest_1min_timeframe(default_conf, fee, mocker, testdatadir) -> None
max_open_trades=1,
position_stacking=False,
)
- assert not results.empty
- assert len(results) == 1
+ assert not results['results'].empty
+ assert len(results['results']) == 1
def test_processed(default_conf, mocker, testdatadir) -> None:
patch_exchange(mocker)
backtesting = Backtesting(default_conf)
+ backtesting._set_strategy(backtesting.strategylist[0])
dict_of_tickerrows = load_data_test('raise', testdatadir)
dataframes = backtesting.strategy.ohlcvdata_to_dataframe(dict_of_tickerrows)
@@ -623,7 +636,7 @@ def test_backtest_pricecontours_protections(default_conf, fee, mocker, testdatad
# While buy-signals are unrealistic, running backtesting
# over and over again should not cause different results
for [contour, numres] in tests:
- assert len(simple_backtest(default_conf, contour, mocker, testdatadir)) == numres
+ assert len(simple_backtest(default_conf, contour, mocker, testdatadir)['results']) == numres
@pytest.mark.parametrize('protections,contour,expected', [
@@ -648,7 +661,7 @@ def test_backtest_pricecontours(default_conf, fee, mocker, testdatadir,
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
# While buy-signals are unrealistic, running backtesting
# over and over again should not cause different results
- assert len(simple_backtest(default_conf, contour, mocker, testdatadir)) == expected
+ assert len(simple_backtest(default_conf, contour, mocker, testdatadir)['results']) == expected
def test_backtest_clash_buy_sell(mocker, default_conf, testdatadir):
@@ -660,10 +673,11 @@ def test_backtest_clash_buy_sell(mocker, default_conf, testdatadir):
backtest_conf = _make_backtest_conf(mocker, conf=default_conf, datadir=testdatadir)
backtesting = Backtesting(default_conf)
+ backtesting._set_strategy(backtesting.strategylist[0])
backtesting.strategy.advise_buy = fun # Override
backtesting.strategy.advise_sell = fun # Override
- results = backtesting.backtest(**backtest_conf)
- assert results.empty
+ result = backtesting.backtest(**backtest_conf)
+ assert result['results'].empty
def test_backtest_only_sell(mocker, default_conf, testdatadir):
@@ -675,10 +689,11 @@ def test_backtest_only_sell(mocker, default_conf, testdatadir):
backtest_conf = _make_backtest_conf(mocker, conf=default_conf, datadir=testdatadir)
backtesting = Backtesting(default_conf)
+ backtesting._set_strategy(backtesting.strategylist[0])
backtesting.strategy.advise_buy = fun # Override
backtesting.strategy.advise_sell = fun # Override
- results = backtesting.backtest(**backtest_conf)
- assert results.empty
+ result = backtesting.backtest(**backtest_conf)
+ assert result['results'].empty
def test_backtest_alternate_buy_sell(default_conf, fee, mocker, testdatadir):
@@ -688,12 +703,14 @@ def test_backtest_alternate_buy_sell(default_conf, fee, mocker, testdatadir):
pair='UNITTEST/BTC', datadir=testdatadir)
default_conf['timeframe'] = '1m'
backtesting = Backtesting(default_conf)
+ backtesting._set_strategy(backtesting.strategylist[0])
backtesting.strategy.advise_buy = _trend_alternate # Override
backtesting.strategy.advise_sell = _trend_alternate # Override
- results = backtesting.backtest(**backtest_conf)
+ result = backtesting.backtest(**backtest_conf)
# 200 candles in backtest data
# won't buy on first (shifted by 1)
# 100 buys signals
+ results = result['results']
assert len(results) == 100
# One trade was force-closed at the end
assert len(results.loc[results['is_open']]) == 0
@@ -729,6 +746,7 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir)
default_conf['timeframe'] = '5m'
backtesting = Backtesting(default_conf)
+ backtesting._set_strategy(backtesting.strategylist[0])
backtesting.strategy.advise_buy = _trend_alternate_hold # Override
backtesting.strategy.advise_sell = _trend_alternate_hold # Override
@@ -745,9 +763,9 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir)
results = backtesting.backtest(**backtest_conf)
# Make sure we have parallel trades
- assert len(evaluate_result_multi(results, '5m', 2)) > 0
+ assert len(evaluate_result_multi(results['results'], '5m', 2)) > 0
# make sure we don't have trades with more than configured max_open_trades
- assert len(evaluate_result_multi(results, '5m', 3)) == 0
+ assert len(evaluate_result_multi(results['results'], '5m', 3)) == 0
backtest_conf = {
'processed': processed,
@@ -757,7 +775,7 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir)
'position_stacking': False,
}
results = backtesting.backtest(**backtest_conf)
- assert len(evaluate_result_multi(results, '5m', 1)) == 0
+ assert len(evaluate_result_multi(results['results'], '5m', 1)) == 0
def test_backtest_start_timerange(default_conf, mocker, caplog, testdatadir):
@@ -789,9 +807,9 @@ def test_backtest_start_timerange(default_conf, mocker, caplog, testdatadir):
'Parameter --timerange detected: 1510694220-1510700340 ...',
f'Using data directory: {testdatadir} ...',
'Loading data from 2017-11-14 20:57:00 '
- 'up to 2017-11-14 22:58:00 (0 days)..',
+ 'up to 2017-11-14 22:58:00 (0 days).',
'Backtesting with data from 2017-11-14 21:17:00 '
- 'up to 2017-11-14 22:58:00 (0 days)..',
+ 'up to 2017-11-14 22:58:00 (0 days).',
'Parameter --enable-position-stacking detected ...'
]
@@ -802,8 +820,20 @@ def test_backtest_start_timerange(default_conf, mocker, caplog, testdatadir):
@pytest.mark.filterwarnings("ignore:deprecated")
def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
+ default_conf['ask_strategy'].update({
+ "use_sell_signal": True,
+ "sell_profit_only": False,
+ "sell_profit_offset": 0.0,
+ "ignore_roi_if_buy_signal": False,
+ })
patch_exchange(mocker)
- backtestmock = MagicMock(return_value=pd.DataFrame(columns=BT_DATA_COLUMNS))
+ backtestmock = MagicMock(return_value={
+ 'results': pd.DataFrame(columns=BT_DATA_COLUMNS),
+ 'config': default_conf,
+ 'locks': [],
+ 'rejected_signals': 20,
+ 'final_balance': 1000,
+ })
mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist',
PropertyMock(return_value=['UNITTEST/BTC']))
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', backtestmock)
@@ -817,7 +847,7 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
text_table_strategy=strattable_mock,
generate_pair_metrics=MagicMock(),
generate_sell_reason_stats=sell_reason_mock,
- generate_strategy_metrics=strat_summary,
+ generate_strategy_comparison=strat_summary,
generate_daily_stats=MagicMock(),
)
patched_configuration_load_config_file(mocker, default_conf)
@@ -851,9 +881,9 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
'Parameter --timerange detected: 1510694220-1510700340 ...',
f'Using data directory: {testdatadir} ...',
'Loading data from 2017-11-14 20:57:00 '
- 'up to 2017-11-14 22:58:00 (0 days)..',
+ 'up to 2017-11-14 22:58:00 (0 days).',
'Backtesting with data from 2017-11-14 21:17:00 '
- 'up to 2017-11-14 22:58:00 (0 days)..',
+ 'up to 2017-11-14 22:58:00 (0 days).',
'Parameter --enable-position-stacking detected ...',
'Running backtesting for Strategy DefaultStrategy',
'Running backtesting for Strategy TestStrategyLegacy',
@@ -865,41 +895,60 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
@pytest.mark.filterwarnings("ignore:deprecated")
def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdatadir, capsys):
-
+ default_conf['ask_strategy'].update({
+ "use_sell_signal": True,
+ "sell_profit_only": False,
+ "sell_profit_offset": 0.0,
+ "ignore_roi_if_buy_signal": False,
+ })
patch_exchange(mocker)
+ result1 = pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC'],
+ 'profit_ratio': [0.0, 0.0],
+ 'profit_abs': [0.0, 0.0],
+ 'open_date': pd.to_datetime(['2018-01-29 18:40:00',
+ '2018-01-30 03:30:00', ], utc=True
+ ),
+ 'close_date': pd.to_datetime(['2018-01-29 20:45:00',
+ '2018-01-30 05:35:00', ], utc=True),
+ 'trade_duration': [235, 40],
+ 'is_open': [False, False],
+ 'stake_amount': [0.01, 0.01],
+ 'open_rate': [0.104445, 0.10302485],
+ 'close_rate': [0.104969, 0.103541],
+ 'sell_reason': [SellType.ROI, SellType.ROI]
+ })
+ result2 = pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC', 'ETH/BTC'],
+ 'profit_ratio': [0.03, 0.01, 0.1],
+ 'profit_abs': [0.01, 0.02, 0.2],
+ 'open_date': pd.to_datetime(['2018-01-29 18:40:00',
+ '2018-01-30 03:30:00',
+ '2018-01-30 05:30:00'], utc=True
+ ),
+ 'close_date': pd.to_datetime(['2018-01-29 20:45:00',
+ '2018-01-30 05:35:00',
+ '2018-01-30 08:30:00'], utc=True),
+ 'trade_duration': [47, 40, 20],
+ 'is_open': [False, False, False],
+ 'stake_amount': [0.01, 0.01, 0.01],
+ 'open_rate': [0.104445, 0.10302485, 0.122541],
+ 'close_rate': [0.104969, 0.103541, 0.123541],
+ 'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
+ })
backtestmock = MagicMock(side_effect=[
- pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC'],
- 'profit_ratio': [0.0, 0.0],
- 'profit_abs': [0.0, 0.0],
- 'open_date': pd.to_datetime(['2018-01-29 18:40:00',
- '2018-01-30 03:30:00', ], utc=True
- ),
- 'close_date': pd.to_datetime(['2018-01-29 20:45:00',
- '2018-01-30 05:35:00', ], utc=True),
- 'trade_duration': [235, 40],
- 'is_open': [False, False],
- 'stake_amount': [0.01, 0.01],
- 'open_rate': [0.104445, 0.10302485],
- 'close_rate': [0.104969, 0.103541],
- 'sell_reason': [SellType.ROI, SellType.ROI]
- }),
- pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC', 'ETH/BTC'],
- 'profit_ratio': [0.03, 0.01, 0.1],
- 'profit_abs': [0.01, 0.02, 0.2],
- 'open_date': pd.to_datetime(['2018-01-29 18:40:00',
- '2018-01-30 03:30:00',
- '2018-01-30 05:30:00'], utc=True
- ),
- 'close_date': pd.to_datetime(['2018-01-29 20:45:00',
- '2018-01-30 05:35:00',
- '2018-01-30 08:30:00'], utc=True),
- 'trade_duration': [47, 40, 20],
- 'is_open': [False, False, False],
- 'stake_amount': [0.01, 0.01, 0.01],
- 'open_rate': [0.104445, 0.10302485, 0.122541],
- 'close_rate': [0.104969, 0.103541, 0.123541],
- 'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
- }),
+ {
+ 'results': result1,
+ 'config': default_conf,
+ 'locks': [],
+ 'rejected_signals': 20,
+ 'final_balance': 1000,
+ },
+ {
+ 'results': result2,
+ 'config': default_conf,
+ 'locks': [],
+ 'rejected_signals': 20,
+ 'final_balance': 1000,
+ }
])
mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist',
PropertyMock(return_value=['UNITTEST/BTC']))
@@ -930,9 +979,9 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat
'Parameter --timerange detected: 1510694220-1510700340 ...',
f'Using data directory: {testdatadir} ...',
'Loading data from 2017-11-14 20:57:00 '
- 'up to 2017-11-14 22:58:00 (0 days)..',
+ 'up to 2017-11-14 22:58:00 (0 days).',
'Backtesting with data from 2017-11-14 21:17:00 '
- 'up to 2017-11-14 22:58:00 (0 days)..',
+ 'up to 2017-11-14 22:58:00 (0 days).',
'Parameter --enable-position-stacking detected ...',
'Running backtesting for Strategy DefaultStrategy',
'Running backtesting for Strategy TestStrategyLegacy',
diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py
index 7cb04fae0..07d208210 100644
--- a/tests/optimize/test_hyperopt.py
+++ b/tests/optimize/test_hyperopt.py
@@ -4,7 +4,7 @@ import re
from datetime import datetime
from pathlib import Path
from typing import Dict, List
-from unittest.mock import MagicMock
+from unittest.mock import ANY, MagicMock
import pandas as pd
import pytest
@@ -17,10 +17,12 @@ from freqtrade.exceptions import OperationalException
from freqtrade.optimize.hyperopt import Hyperopt
from freqtrade.optimize.hyperopt_auto import HyperOptAuto
from freqtrade.optimize.hyperopt_tools import HyperoptTools
+from freqtrade.optimize.optimize_reports import generate_strategy_stats
from freqtrade.optimize.space import SKDecimal
from freqtrade.resolvers.hyperopt_resolver import HyperOptResolver
from freqtrade.state import RunMode
from freqtrade.strategy.hyper import IntParameter
+from freqtrade.strategy.interface import SellType
from tests.conftest import (get_args, log_has, log_has_re, patch_exchange,
patched_configuration_load_config_file)
@@ -28,23 +30,7 @@ from .hyperopts.default_hyperopt import DefaultHyperOpt
# Functions for recurrent object patching
-def create_results(mocker, hyperopt, testdatadir) -> List[Dict]:
- """
- When creating results, mock the hyperopt so that *by default*
- - we don't create any pickle'd files in the filesystem
- - we might have a pickle'd file so make sure that we return
- false when looking for it
- """
- hyperopt.results_file = testdatadir / 'optimize/ut_results.pickle'
-
- mocker.patch.object(Path, "is_file", MagicMock(return_value=False))
- stat_mock = MagicMock()
- stat_mock.st_size = 1
- mocker.patch.object(Path, "stat", MagicMock(return_value=stat_mock))
-
- mocker.patch.object(Path, "unlink", MagicMock(return_value=True))
- mocker.patch('freqtrade.optimize.hyperopt.dump', return_value=None)
- mocker.patch('freqtrade.optimize.hyperopt.file_dump_json')
+def create_results() -> List[Dict]:
return [{'loss': 1, 'result': 'foo', 'params': {}, 'is_best': True}]
@@ -318,54 +304,49 @@ def test_no_log_if_loss_does_not_improve(hyperopt, caplog) -> None:
assert caplog.record_tuples == []
-def test_save_results_saves_epochs(mocker, hyperopt, testdatadir, caplog) -> None:
- epochs = create_results(mocker, hyperopt, testdatadir)
- mock_dump = mocker.patch('freqtrade.optimize.hyperopt.dump', return_value=None)
- mock_dump_json = mocker.patch('freqtrade.optimize.hyperopt.file_dump_json', return_value=None)
- results_file = testdatadir / 'optimize' / 'ut_results.pickle'
+def test_save_results_saves_epochs(mocker, hyperopt, tmpdir, caplog) -> None:
+ # Test writing to temp dir and reading again
+ epochs = create_results()
+ hyperopt.results_file = Path(tmpdir / 'ut_results.fthypt')
caplog.set_level(logging.DEBUG)
- hyperopt.epochs = epochs
- hyperopt._save_results()
- assert log_has(f"1 epoch saved to '{results_file}'.", caplog)
- mock_dump.assert_called_once()
- mock_dump_json.assert_called_once()
+ for epoch in epochs:
+ hyperopt._save_result(epoch)
+ assert log_has(f"1 epoch saved to '{hyperopt.results_file}'.", caplog)
- hyperopt.epochs = epochs + epochs
- hyperopt._save_results()
- assert log_has(f"2 epochs saved to '{results_file}'.", caplog)
+ hyperopt._save_result(epochs[0])
+ assert log_has(f"2 epochs saved to '{hyperopt.results_file}'.", caplog)
+
+ hyperopt_epochs = HyperoptTools.load_previous_results(hyperopt.results_file)
+ assert len(hyperopt_epochs) == 2
-def test_read_results_returns_epochs(mocker, hyperopt, testdatadir, caplog) -> None:
- epochs = create_results(mocker, hyperopt, testdatadir)
- mock_load = mocker.patch('freqtrade.optimize.hyperopt_tools.load', return_value=epochs)
- results_file = testdatadir / 'optimize' / 'ut_results.pickle'
- hyperopt_epochs = HyperoptTools._read_results(results_file)
- assert log_has(f"Reading epochs from '{results_file}'", caplog)
- assert hyperopt_epochs == epochs
- mock_load.assert_called_once()
+def test_load_previous_results(testdatadir, caplog) -> None:
-
-def test_load_previous_results(mocker, hyperopt, testdatadir, caplog) -> None:
- epochs = create_results(mocker, hyperopt, testdatadir)
- mock_load = mocker.patch('freqtrade.optimize.hyperopt_tools.load', return_value=epochs)
- mocker.patch.object(Path, 'is_file', MagicMock(return_value=True))
- statmock = MagicMock()
- statmock.st_size = 5
- # mocker.patch.object(Path, 'stat', MagicMock(return_value=statmock))
-
- results_file = testdatadir / 'optimize' / 'ut_results.pickle'
+ results_file = testdatadir / 'hyperopt_results_SampleStrategy.pickle'
hyperopt_epochs = HyperoptTools.load_previous_results(results_file)
- assert hyperopt_epochs == epochs
- mock_load.assert_called_once()
+ assert len(hyperopt_epochs) == 5
+ assert log_has_re(r"Reading pickled epochs from .*", caplog)
- del epochs[0]['is_best']
- mock_load = mocker.patch('freqtrade.optimize.hyperopt_tools.load', return_value=epochs)
+ caplog.clear()
- with pytest.raises(OperationalException):
+ # Modern version
+ results_file = testdatadir / 'strategy_SampleStrategy.fthypt'
+
+ hyperopt_epochs = HyperoptTools.load_previous_results(results_file)
+
+ assert len(hyperopt_epochs) == 5
+ assert log_has_re(r"Reading epochs from .*", caplog)
+
+
+def test_load_previous_results2(mocker, testdatadir, caplog) -> None:
+ mocker.patch('freqtrade.optimize.hyperopt_tools.HyperoptTools._read_results_pickle',
+ return_value=[{'asdf': '222'}])
+ results_file = testdatadir / 'hyperopt_results_SampleStrategy.pickle'
+ with pytest.raises(OperationalException, match=r"The file .* incompatible.*"):
HyperoptTools.load_previous_results(results_file)
@@ -383,7 +364,8 @@ def test_roi_table_generation(hyperopt) -> None:
def test_start_calls_optimizer(mocker, hyperopt_conf, capsys) -> None:
- dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock())
+ dumper = mocker.patch('freqtrade.optimize.hyperopt.dump')
+ dumper2 = mocker.patch('freqtrade.optimize.hyperopt.Hyperopt._save_result')
mocker.patch('freqtrade.optimize.hyperopt.file_dump_json')
mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data',
@@ -422,9 +404,9 @@ def test_start_calls_optimizer(mocker, hyperopt_conf, capsys) -> None:
out, err = capsys.readouterr()
assert 'Best result:\n\n* 1/1: foo result Objective: 1.00000\n' in out
- assert dumper.called
- # Should be called twice, once for historical candle data, once to save evaluations
- assert dumper.call_count == 2
+ # Should be called for historical candle data
+ assert dumper.call_count == 1
+ assert dumper2.call_count == 1
assert hasattr(hyperopt.backtesting.strategy, "advise_sell")
assert hasattr(hyperopt.backtesting.strategy, "advise_buy")
assert hasattr(hyperopt, "max_open_trades")
@@ -432,18 +414,42 @@ def test_start_calls_optimizer(mocker, hyperopt_conf, capsys) -> None:
assert hasattr(hyperopt, "position_stacking")
-def test_format_results(hyperopt):
- # Test with BTC as stake_currency
- trades = [
- ('ETH/BTC', 2, 2, 123),
- ('LTC/BTC', 1, 1, 123),
- ('XPR/BTC', -1, -2, -246)
- ]
- labels = ['currency', 'profit_ratio', 'profit_abs', 'trade_duration']
- df = pd.DataFrame.from_records(trades, columns=labels)
- results_metrics = hyperopt._calculate_results_metrics(df)
- results_explanation = hyperopt._format_results_explanation_string(results_metrics)
- total_profit = results_metrics['total_profit']
+def test_hyperopt_format_results(hyperopt):
+
+ bt_result = {
+ 'results': pd.DataFrame({"pair": ["UNITTEST/BTC", "UNITTEST/BTC",
+ "UNITTEST/BTC", "UNITTEST/BTC"],
+ "profit_ratio": [0.003312, 0.010801, 0.013803, 0.002780],
+ "profit_abs": [0.000003, 0.000011, 0.000014, 0.000003],
+ "open_date": [Arrow(2017, 11, 14, 19, 32, 00).datetime,
+ Arrow(2017, 11, 14, 21, 36, 00).datetime,
+ Arrow(2017, 11, 14, 22, 12, 00).datetime,
+ Arrow(2017, 11, 14, 22, 44, 00).datetime],
+ "close_date": [Arrow(2017, 11, 14, 21, 35, 00).datetime,
+ Arrow(2017, 11, 14, 22, 10, 00).datetime,
+ Arrow(2017, 11, 14, 22, 43, 00).datetime,
+ Arrow(2017, 11, 14, 22, 58, 00).datetime],
+ "open_rate": [0.002543, 0.003003, 0.003089, 0.003214],
+ "close_rate": [0.002546, 0.003014, 0.003103, 0.003217],
+ "trade_duration": [123, 34, 31, 14],
+ "is_open": [False, False, False, True],
+ "stake_amount": [0.01, 0.01, 0.01, 0.01],
+ "sell_reason": [SellType.ROI, SellType.STOP_LOSS,
+ SellType.ROI, SellType.FORCE_SELL]
+ }),
+ 'config': hyperopt.config,
+ 'locks': [],
+ 'final_balance': 0.02,
+ 'rejected_signals': 2,
+ 'backtest_start_time': 1619718665,
+ 'backtest_end_time': 1619718665,
+ }
+ results_metrics = generate_strategy_stats({'XRP/BTC': None}, '', bt_result,
+ Arrow(2017, 11, 14, 19, 32, 00),
+ Arrow(2017, 12, 14, 19, 32, 00), market_change=0)
+
+ results_explanation = HyperoptTools.format_results_explanation_string(results_metrics, 'BTC')
+ total_profit = results_metrics['profit_total_abs']
results = {
'loss': 0.0,
@@ -457,21 +463,9 @@ def test_format_results(hyperopt):
}
result = HyperoptTools._format_explanation_string(results, 1)
- assert result.find(' 66.67%')
- assert result.find('Total profit 1.00000000 BTC')
- assert result.find('2.0000Σ %')
-
- # Test with EUR as stake_currency
- trades = [
- ('ETH/EUR', 2, 2, 123),
- ('LTC/EUR', 1, 1, 123),
- ('XPR/EUR', -1, -2, -246)
- ]
- df = pd.DataFrame.from_records(trades, columns=labels)
- results_metrics = hyperopt._calculate_results_metrics(df)
- results['total_profit'] = results_metrics['total_profit']
- result = HyperoptTools._format_explanation_string(results, 1)
- assert result.find('Total profit 1.00000000 EUR')
+ assert ' 0.71%' in result
+ assert 'Total profit 0.00003100 BTC' in result
+ assert '0:50:00 min' in result
@pytest.mark.parametrize("spaces, expected_results", [
@@ -502,10 +496,10 @@ def test_format_results(hyperopt):
(['default', 'buy'],
{'buy': True, 'sell': True, 'roi': True, 'stoploss': True, 'trailing': False}),
])
-def test_has_space(hyperopt, spaces, expected_results):
+def test_has_space(hyperopt_conf, spaces, expected_results):
for s in ['buy', 'sell', 'roi', 'stoploss', 'trailing']:
- hyperopt.config.update({'spaces': spaces})
- assert hyperopt.has_space(s) == expected_results[s]
+ hyperopt_conf.update({'spaces': spaces})
+ assert HyperoptTools.has_space(hyperopt_conf, s) == expected_results[s]
def test_populate_indicators(hyperopt, testdatadir) -> None:
@@ -576,22 +570,39 @@ def test_generate_optimizer(mocker, hyperopt_conf) -> None:
'hyperopt_min_trades': 1,
})
- trades = [
- ('TRX/BTC', 0.023117, 0.000233, 100)
- ]
- labels = ['currency', 'profit_ratio', 'profit_abs', 'trade_duration']
- backtest_result = pd.DataFrame.from_records(trades, columns=labels)
+ backtest_result = {
+ 'results': pd.DataFrame({"pair": ["UNITTEST/BTC", "UNITTEST/BTC",
+ "UNITTEST/BTC", "UNITTEST/BTC"],
+ "profit_ratio": [0.003312, 0.010801, 0.013803, 0.002780],
+ "profit_abs": [0.000003, 0.000011, 0.000014, 0.000003],
+ "open_date": [Arrow(2017, 11, 14, 19, 32, 00).datetime,
+ Arrow(2017, 11, 14, 21, 36, 00).datetime,
+ Arrow(2017, 11, 14, 22, 12, 00).datetime,
+ Arrow(2017, 11, 14, 22, 44, 00).datetime],
+ "close_date": [Arrow(2017, 11, 14, 21, 35, 00).datetime,
+ Arrow(2017, 11, 14, 22, 10, 00).datetime,
+ Arrow(2017, 11, 14, 22, 43, 00).datetime,
+ Arrow(2017, 11, 14, 22, 58, 00).datetime],
+ "open_rate": [0.002543, 0.003003, 0.003089, 0.003214],
+ "close_rate": [0.002546, 0.003014, 0.003103, 0.003217],
+ "trade_duration": [123, 34, 31, 14],
+ "is_open": [False, False, False, True],
+ "stake_amount": [0.01, 0.01, 0.01, 0.01],
+ "sell_reason": [SellType.ROI, SellType.STOP_LOSS,
+ SellType.ROI, SellType.FORCE_SELL]
+ }),
+ 'config': hyperopt_conf,
+ 'locks': [],
+ 'rejected_signals': 20,
+ 'final_balance': 1000,
+ }
- mocker.patch(
- 'freqtrade.optimize.hyperopt.Backtesting.backtest',
- MagicMock(return_value=backtest_result)
- )
- mocker.patch(
- 'freqtrade.optimize.hyperopt.get_timerange',
- MagicMock(return_value=(Arrow(2017, 12, 10), Arrow(2017, 12, 13)))
- )
+ mocker.patch('freqtrade.optimize.hyperopt.Backtesting.backtest', return_value=backtest_result)
+ mocker.patch('freqtrade.optimize.hyperopt.get_timerange',
+ return_value=(Arrow(2017, 12, 10), Arrow(2017, 12, 13)))
patch_exchange(mocker)
- mocker.patch('freqtrade.optimize.hyperopt.load', MagicMock())
+ mocker.patch.object(Path, 'open')
+ mocker.patch('freqtrade.optimize.hyperopt.load', return_value={'XRP/BTC': None})
optimizer_param = {
'adx-value': 0,
@@ -625,11 +636,11 @@ def test_generate_optimizer(mocker, hyperopt_conf) -> None:
'trailing_only_offset_is_reached': False,
}
response_expected = {
- 'loss': 1.9840569076926293,
- 'results_explanation': (' 1 trades. 1/0/0 Wins/Draws/Losses. '
- 'Avg profit 2.31%. Median profit 2.31%. Total profit '
- '0.00023300 BTC ( 2.31%). '
- 'Avg duration 100.0 min.'
+ 'loss': 1.9147239021396234,
+ 'results_explanation': (' 4 trades. 4/0/0 Wins/Draws/Losses. '
+ 'Avg profit 0.77%. Median profit 0.71%. Total profit '
+ '0.00003100 BTC ( 0.00%). '
+ 'Avg duration 0:50:00 min.'
),
'params_details': {'buy': {'adx-enabled': False,
'adx-value': 0,
@@ -640,10 +651,10 @@ def test_generate_optimizer(mocker, hyperopt_conf) -> None:
'rsi-enabled': False,
'rsi-value': 0,
'trigger': 'macd_cross_signal'},
- 'roi': {0: 0.12000000000000001,
- 20.0: 0.02,
- 50.0: 0.01,
- 110.0: 0},
+ 'roi': {"0": 0.12000000000000001,
+ "20.0": 0.02,
+ "50.0": 0.01,
+ "110.0": 0},
'sell': {'sell-adx-enabled': False,
'sell-adx-value': 0,
'sell-fastd-enabled': True,
@@ -659,21 +670,16 @@ def test_generate_optimizer(mocker, hyperopt_conf) -> None:
'trailing_stop_positive': 0.02,
'trailing_stop_positive_offset': 0.07}},
'params_dict': optimizer_param,
- 'results_metrics': {'avg_profit': 2.3117,
- 'draws': 0,
- 'duration': 100.0,
- 'losses': 0,
- 'winsdrawslosses': ' 1 0 0',
- 'median_profit': 2.3117,
- 'profit': 2.3117,
- 'total_profit': 0.000233,
- 'trade_count': 1,
- 'wins': 1},
- 'total_profit': 0.00023300
+ 'params_not_optimized': {'buy': {}, 'sell': {}},
+ 'results_metrics': ANY,
+ 'total_profit': 3.1e-08
}
hyperopt = Hyperopt(hyperopt_conf)
- hyperopt.dimensions = hyperopt.hyperopt_space()
+ hyperopt.min_date = Arrow(2017, 12, 10)
+ hyperopt.max_date = Arrow(2017, 12, 13)
+ hyperopt.init_spaces()
+ hyperopt.dimensions = hyperopt.dimensions
generate_optimizer_value = hyperopt.generate_optimizer(list(optimizer_param.values()))
assert generate_optimizer_value == response_expected
@@ -690,7 +696,8 @@ def test_clean_hyperopt(mocker, hyperopt_conf, caplog):
def test_print_json_spaces_all(mocker, hyperopt_conf, capsys) -> None:
- dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock())
+ dumper = mocker.patch('freqtrade.optimize.hyperopt.dump')
+ dumper2 = mocker.patch('freqtrade.optimize.hyperopt.Hyperopt._save_result')
mocker.patch('freqtrade.optimize.hyperopt.file_dump_json')
mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data',
@@ -741,13 +748,14 @@ def test_print_json_spaces_all(mocker, hyperopt_conf, capsys) -> None:
':{},"stoploss":null,"trailing_stop":null}'
)
assert result_str in out # noqa: E501
- assert dumper.called
- # Should be called twice, once for historical candle data, once to save evaluations
- assert dumper.call_count == 2
+ # Should be called for historical candle data
+ assert dumper.call_count == 1
+ assert dumper2.call_count == 1
def test_print_json_spaces_default(mocker, hyperopt_conf, capsys) -> None:
- dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock())
+ dumper = mocker.patch('freqtrade.optimize.hyperopt.dump')
+ dumper2 = mocker.patch('freqtrade.optimize.hyperopt.Hyperopt._save_result')
mocker.patch('freqtrade.optimize.hyperopt.file_dump_json')
mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data',
MagicMock(return_value=(MagicMock(), None)))
@@ -789,13 +797,14 @@ def test_print_json_spaces_default(mocker, hyperopt_conf, capsys) -> None:
out, err = capsys.readouterr()
assert '{"params":{"mfi-value":null,"sell-mfi-value":null},"minimal_roi":{},"stoploss":null}' in out # noqa: E501
- assert dumper.called
- # Should be called twice, once for historical candle data, once to save evaluations
- assert dumper.call_count == 2
+ # Should be called for historical candle data
+ assert dumper.call_count == 1
+ assert dumper2.call_count == 1
def test_print_json_spaces_roi_stoploss(mocker, hyperopt_conf, capsys) -> None:
- dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock())
+ dumper = mocker.patch('freqtrade.optimize.hyperopt.dump')
+ dumper2 = mocker.patch('freqtrade.optimize.hyperopt.Hyperopt._save_result')
mocker.patch('freqtrade.optimize.hyperopt.file_dump_json')
mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data',
MagicMock(return_value=(MagicMock(), None)))
@@ -836,13 +845,14 @@ def test_print_json_spaces_roi_stoploss(mocker, hyperopt_conf, capsys) -> None:
out, err = capsys.readouterr()
assert '{"minimal_roi":{},"stoploss":null}' in out
- assert dumper.called
- # Should be called twice, once for historical candle data, once to save evaluations
- assert dumper.call_count == 2
+
+ assert dumper.call_count == 1
+ assert dumper2.call_count == 1
def test_simplified_interface_roi_stoploss(mocker, hyperopt_conf, capsys) -> None:
- dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock())
+ dumper = mocker.patch('freqtrade.optimize.hyperopt.dump')
+ dumper2 = mocker.patch('freqtrade.optimize.hyperopt.Hyperopt._save_result')
mocker.patch('freqtrade.optimize.hyperopt.file_dump_json')
mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data',
MagicMock(return_value=(MagicMock(), None)))
@@ -884,9 +894,9 @@ def test_simplified_interface_roi_stoploss(mocker, hyperopt_conf, capsys) -> Non
out, err = capsys.readouterr()
assert 'Best result:\n\n* 1/1: foo result Objective: 1.00000\n' in out
- assert dumper.called
- # Should be called twice, once for historical candle data, once to save evaluations
- assert dumper.call_count == 2
+ assert dumper.call_count == 1
+ assert dumper2.call_count == 1
+
assert hasattr(hyperopt.backtesting.strategy, "advise_sell")
assert hasattr(hyperopt.backtesting.strategy, "advise_buy")
assert hasattr(hyperopt, "max_open_trades")
@@ -922,7 +932,8 @@ def test_simplified_interface_all_failed(mocker, hyperopt_conf) -> None:
def test_simplified_interface_buy(mocker, hyperopt_conf, capsys) -> None:
- dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock())
+ dumper = mocker.patch('freqtrade.optimize.hyperopt.dump')
+ dumper2 = mocker.patch('freqtrade.optimize.hyperopt.Hyperopt._save_result')
mocker.patch('freqtrade.optimize.hyperopt.file_dump_json')
mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data',
MagicMock(return_value=(MagicMock(), None)))
@@ -965,8 +976,8 @@ def test_simplified_interface_buy(mocker, hyperopt_conf, capsys) -> None:
out, err = capsys.readouterr()
assert 'Best result:\n\n* 1/1: foo result Objective: 1.00000\n' in out
assert dumper.called
- # Should be called twice, once for historical candle data, once to save evaluations
- assert dumper.call_count == 2
+ assert dumper.call_count == 1
+ assert dumper2.call_count == 1
assert hasattr(hyperopt.backtesting.strategy, "advise_sell")
assert hasattr(hyperopt.backtesting.strategy, "advise_buy")
assert hasattr(hyperopt, "max_open_trades")
@@ -975,7 +986,8 @@ def test_simplified_interface_buy(mocker, hyperopt_conf, capsys) -> None:
def test_simplified_interface_sell(mocker, hyperopt_conf, capsys) -> None:
- dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock())
+ dumper = mocker.patch('freqtrade.optimize.hyperopt.dump')
+ dumper2 = mocker.patch('freqtrade.optimize.hyperopt.Hyperopt._save_result')
mocker.patch('freqtrade.optimize.hyperopt.file_dump_json')
mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data',
MagicMock(return_value=(MagicMock(), None)))
@@ -1018,8 +1030,8 @@ def test_simplified_interface_sell(mocker, hyperopt_conf, capsys) -> None:
out, err = capsys.readouterr()
assert 'Best result:\n\n* 1/1: foo result Objective: 1.00000\n' in out
assert dumper.called
- # Should be called twice, once for historical candle data, once to save evaluations
- assert dumper.call_count == 2
+ assert dumper.call_count == 1
+ assert dumper2.call_count == 1
assert hasattr(hyperopt.backtesting.strategy, "advise_sell")
assert hasattr(hyperopt.backtesting.strategy, "advise_buy")
assert hasattr(hyperopt, "max_open_trades")
@@ -1107,7 +1119,7 @@ def test_in_strategy_auto_hyperopt(mocker, hyperopt_conf, tmpdir, fee) -> None:
assert isinstance(hyperopt.custom_hyperopt, HyperOptAuto)
assert isinstance(hyperopt.backtesting.strategy.buy_rsi, IntParameter)
- assert hyperopt.backtesting.strategy.buy_rsi.hyperopt is True
+ assert hyperopt.backtesting.strategy.buy_rsi.in_space is True
assert hyperopt.backtesting.strategy.buy_rsi.value == 35
buy_rsi_range = hyperopt.backtesting.strategy.buy_rsi.range
assert isinstance(buy_rsi_range, range)
@@ -1132,3 +1144,17 @@ def test_SKDecimal():
assert space.transform([2.0]) == [200]
assert space.transform([1.0]) == [100]
assert space.transform([1.5, 1.6]) == [150, 160]
+
+
+def test___pprint():
+ params = {'buy_std': 1.2, 'buy_rsi': 31, 'buy_enable': True, 'buy_what': 'asdf'}
+ non_params = {'buy_notoptimied': 55}
+
+ x = HyperoptTools._pprint(params, non_params)
+ assert x == """{
+ "buy_std": 1.2,
+ "buy_rsi": 31,
+ "buy_enable": True,
+ "buy_what": "asdf",
+ "buy_notoptimied": 55, # value loaded from strategy
+}"""
diff --git a/tests/optimize/test_optimize_reports.py b/tests/optimize/test_optimize_reports.py
index 8119c732b..f9dac3397 100644
--- a/tests/optimize/test_optimize_reports.py
+++ b/tests/optimize/test_optimize_reports.py
@@ -1,5 +1,6 @@
+import datetime
import re
-from datetime import datetime, timedelta, timezone
+from datetime import timedelta
from pathlib import Path
import pandas as pd
@@ -7,14 +8,15 @@ import pytest
from arrow import Arrow
from freqtrade.configuration import TimeRange
-from freqtrade.constants import LAST_BT_RESULT_FN
+from freqtrade.constants import DATETIME_PRINT_FORMAT, LAST_BT_RESULT_FN
from freqtrade.data import history
from freqtrade.data.btanalysis import get_latest_backtest_filename, load_backtest_data
from freqtrade.edge import PairInfo
from freqtrade.optimize.optimize_reports import (generate_backtest_stats, generate_daily_stats,
generate_edge_table, generate_pair_metrics,
generate_sell_reason_stats,
- generate_strategy_metrics, store_backtest_stats,
+ generate_strategy_comparison,
+ generate_trading_stats, store_backtest_stats,
text_table_bt_results, text_table_sell_reason,
text_table_strategy)
from freqtrade.resolvers.strategy_resolver import StrategyResolver
@@ -26,25 +28,22 @@ def test_text_table_bt_results():
results = pd.DataFrame(
{
- 'pair': ['ETH/BTC', 'ETH/BTC'],
- 'profit_ratio': [0.1, 0.2],
- 'profit_abs': [0.2, 0.4],
- 'trade_duration': [10, 30],
- 'wins': [2, 0],
- 'draws': [0, 0],
- 'losses': [0, 0]
+ 'pair': ['ETH/BTC', 'ETH/BTC', 'ETH/BTC'],
+ 'profit_ratio': [0.1, 0.2, -0.05],
+ 'profit_abs': [0.2, 0.4, -0.1],
+ 'trade_duration': [10, 30, 20],
}
)
result_str = (
- '| Pair | Buys | Avg Profit % | Cum Profit % | Tot Profit BTC |'
- ' Tot Profit % | Avg Duration | Wins | Draws | Losses |\n'
- '|---------+--------+----------------+----------------+------------------+'
- '----------------+----------------+--------+---------+----------|\n'
- '| ETH/BTC | 2 | 15.00 | 30.00 | 0.60000000 |'
- ' 15.00 | 0:20:00 | 2 | 0 | 0 |\n'
- '| TOTAL | 2 | 15.00 | 30.00 | 0.60000000 |'
- ' 15.00 | 0:20:00 | 2 | 0 | 0 |'
+ '| Pair | Buys | Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % |'
+ ' Avg Duration | Win Draw Loss Win% |\n'
+ '|---------+--------+----------------+----------------+------------------+----------------+'
+ '----------------+-------------------------|\n'
+ '| ETH/BTC | 3 | 8.33 | 25.00 | 0.50000000 | 12.50 |'
+ ' 0:20:00 | 2 0 1 66.7 |\n'
+ '| TOTAL | 3 | 8.33 | 25.00 | 0.50000000 | 12.50 |'
+ ' 0:20:00 | 2 0 1 66.7 |'
)
pair_results = generate_pair_metrics(data={'ETH/BTC': {}}, stake_currency='BTC',
@@ -80,6 +79,7 @@ def test_generate_backtest_stats(default_conf, testdatadir):
'config': default_conf,
'locks': [],
'final_balance': 1000.02,
+ 'rejected_signals': 20,
'backtest_start_time': Arrow.utcnow().int_timestamp,
'backtest_end_time': Arrow.utcnow().int_timestamp,
}
@@ -96,8 +96,8 @@ def test_generate_backtest_stats(default_conf, testdatadir):
assert 'DefStrat' in stats['strategy']
assert 'strategy_comparison' in stats
strat_stats = stats['strategy']['DefStrat']
- assert strat_stats['backtest_start'] == min_date.datetime
- assert strat_stats['backtest_end'] == max_date.datetime
+ assert strat_stats['backtest_start'] == min_date.strftime(DATETIME_PRINT_FORMAT)
+ assert strat_stats['backtest_end'] == max_date.strftime(DATETIME_PRINT_FORMAT)
assert strat_stats['total_trades'] == len(results['DefStrat']['results'])
# Above sample had no loosing trade
assert strat_stats['max_drawdown'] == 0.0
@@ -127,6 +127,7 @@ def test_generate_backtest_stats(default_conf, testdatadir):
'config': default_conf,
'locks': [],
'final_balance': 1000.02,
+ 'rejected_signals': 20,
'backtest_start_time': Arrow.utcnow().int_timestamp,
'backtest_end_time': Arrow.utcnow().int_timestamp,
}
@@ -140,8 +141,8 @@ def test_generate_backtest_stats(default_conf, testdatadir):
strat_stats = stats['strategy']['DefStrat']
assert strat_stats['max_drawdown'] == 0.013803
- assert strat_stats['drawdown_start'] == datetime(2017, 11, 14, 22, 10, tzinfo=timezone.utc)
- assert strat_stats['drawdown_end'] == datetime(2017, 11, 14, 22, 43, tzinfo=timezone.utc)
+ assert strat_stats['drawdown_start'] == '2017-11-14 22:10:00'
+ assert strat_stats['drawdown_end'] == '2017-11-14 22:43:00'
assert strat_stats['drawdown_end_ts'] == 1510699380000
assert strat_stats['drawdown_start_ts'] == 1510697400000
assert strat_stats['pairlist'] == ['UNITTEST/BTC']
@@ -226,8 +227,6 @@ def test_generate_daily_stats(testdatadir):
assert res['winning_days'] == 14
assert res['draw_days'] == 4
assert res['losing_days'] == 3
- assert res['winner_holding_avg'] == timedelta(seconds=1440)
- assert res['loser_holding_avg'] == timedelta(days=1, seconds=21420)
# Select empty dataframe!
res = generate_daily_stats(bt_data.loc[bt_data['open_date'] == '2000-01-01', :])
@@ -238,6 +237,23 @@ def test_generate_daily_stats(testdatadir):
assert res['losing_days'] == 0
+def test_generate_trading_stats(testdatadir):
+ filename = testdatadir / "backtest-result_new.json"
+ bt_data = load_backtest_data(filename)
+ res = generate_trading_stats(bt_data)
+ assert isinstance(res, dict)
+ assert res['winner_holding_avg'] == timedelta(seconds=1440)
+ assert res['loser_holding_avg'] == timedelta(days=1, seconds=21420)
+ assert 'wins' in res
+ assert 'losses' in res
+ assert 'draws' in res
+
+ # Select empty dataframe!
+ res = generate_trading_stats(bt_data.loc[bt_data['open_date'] == '2000-01-01', :])
+ assert res['wins'] == 0
+ assert res['losses'] == 0
+
+
def test_text_table_sell_reason():
results = pd.DataFrame(
@@ -254,14 +270,14 @@ def test_text_table_sell_reason():
)
result_str = (
- '| Sell Reason | Sells | Wins | Draws | Losses |'
- ' Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % |\n'
- '|---------------+---------+--------+---------+----------+'
- '----------------+----------------+------------------+----------------|\n'
- '| roi | 2 | 2 | 0 | 0 |'
- ' 15 | 30 | 0.6 | 15 |\n'
- '| stop_loss | 1 | 0 | 0 | 1 |'
- ' -10 | -10 | -0.2 | -5 |'
+ '| Sell Reason | Sells | Win Draws Loss Win% | Avg Profit % | Cum Profit % |'
+ ' Tot Profit BTC | Tot Profit % |\n'
+ '|---------------+---------+--------------------------+----------------+----------------+'
+ '------------------+----------------|\n'
+ '| roi | 2 | 2 0 0 100 | 15 | 30 |'
+ ' 0.6 | 15 |\n'
+ '| stop_loss | 1 | 0 0 1 0 | -10 | -10 |'
+ ' -0.2 | -5 |'
)
sell_reason_stats = generate_sell_reason_stats(max_open_trades=2,
@@ -309,9 +325,12 @@ def test_text_table_strategy(default_conf):
default_conf['max_open_trades'] = 2
default_conf['dry_run_wallet'] = 3
results = {}
+ date = datetime.datetime(year=2020, month=1, day=1, hour=12, minute=30)
+ delta = datetime.timedelta(days=1)
results['TestStrategy1'] = {'results': pd.DataFrame(
{
'pair': ['ETH/BTC', 'ETH/BTC', 'ETH/BTC'],
+ 'close_date': [date, date + delta, date + delta * 2],
'profit_ratio': [0.1, 0.2, 0.3],
'profit_abs': [0.2, 0.4, 0.5],
'trade_duration': [10, 30, 10],
@@ -324,6 +343,7 @@ def test_text_table_strategy(default_conf):
results['TestStrategy2'] = {'results': pd.DataFrame(
{
'pair': ['LTC/BTC', 'LTC/BTC', 'LTC/BTC'],
+ 'close_date': [date, date + delta, date + delta * 2],
'profit_ratio': [0.4, 0.2, 0.3],
'profit_abs': [0.4, 0.4, 0.5],
'trade_duration': [15, 30, 15],
@@ -335,18 +355,17 @@ def test_text_table_strategy(default_conf):
), 'config': default_conf}
result_str = (
- '| Strategy | Buys | Avg Profit % | Cum Profit % | Tot'
- ' Profit BTC | Tot Profit % | Avg Duration | Wins | Draws | Losses |\n'
+ '| Strategy | Buys | Avg Profit % | Cum Profit % | Tot Profit BTC |'
+ ' Tot Profit % | Avg Duration | Win Draw Loss Win% | Drawdown |\n'
'|---------------+--------+----------------+----------------+------------------+'
- '----------------+----------------+--------+---------+----------|\n'
+ '----------------+----------------+-------------------------+-----------------------|\n'
'| TestStrategy1 | 3 | 20.00 | 60.00 | 1.10000000 |'
- ' 36.67 | 0:17:00 | 3 | 0 | 0 |\n'
+ ' 36.67 | 0:17:00 | 3 0 0 100 | 0.00000000 BTC 0.00% |\n'
'| TestStrategy2 | 3 | 30.00 | 90.00 | 1.30000000 |'
- ' 43.33 | 0:20:00 | 3 | 0 | 0 |'
+ ' 43.33 | 0:20:00 | 3 0 0 100 | 0.00000000 BTC 0.00% |'
)
- strategy_results = generate_strategy_metrics(all_results=results)
-
+ strategy_results = generate_strategy_comparison(all_results=results)
assert text_table_strategy(strategy_results, 'BTC') == result_str
diff --git a/tests/plugins/test_pairlist.py b/tests/plugins/test_pairlist.py
index 8b060c287..5e2274ce3 100644
--- a/tests/plugins/test_pairlist.py
+++ b/tests/plugins/test_pairlist.py
@@ -7,10 +7,11 @@ import pytest
from freqtrade.constants import AVAILABLE_PAIRLISTS
from freqtrade.exceptions import OperationalException
+from freqtrade.persistence import Trade
from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist
from freqtrade.plugins.pairlistmanager import PairListManager
from freqtrade.resolvers import PairListResolver
-from tests.conftest import get_patched_freqtradebot, log_has, log_has_re
+from tests.conftest import get_patched_exchange, get_patched_freqtradebot, log_has, log_has_re
@pytest.fixture(scope="function")
@@ -406,6 +407,9 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf):
([{"method": "VolumePairList", "number_assets": 20, "sort_key": "quoteVolume"},
{"method": "PriceFilter", "low_price_ratio": 0.02}],
"USDT", ['ETH/USDT', 'NANO/USDT']),
+ ([{"method": "VolumePairList", "number_assets": 20, "sort_key": "quoteVolume"},
+ {"method": "PriceFilter", "max_value": 0.000001}],
+ "USDT", ['NANO/USDT']),
([{"method": "StaticPairList"},
{"method": "RangeStabilityFilter", "lookback_days": 10,
"min_rate_of_change": 0.01, "refresh_period": 1440}],
@@ -488,6 +492,8 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, t
r'because last price < .*%$', caplog) or
log_has_re(r'^Removed .* from whitelist, '
r'because last price > .*%$', caplog) or
+ log_has_re(r'^Removed .* from whitelist, '
+ r'because min value change of .*', caplog) or
log_has_re(r"^Removed .* from whitelist, because ticker\['last'\] "
r"is empty.*", caplog))
if pairlist['method'] == 'VolumePairList':
@@ -512,6 +518,18 @@ def test_PrecisionFilter_error(mocker, whitelist_conf) -> None:
PairListManager(MagicMock, whitelist_conf)
+def test_PerformanceFilter_error(mocker, whitelist_conf, caplog) -> None:
+ whitelist_conf['pairlists'] = [{"method": "StaticPairList"}, {"method": "PerformanceFilter"}]
+ if hasattr(Trade, 'query'):
+ del Trade.query
+ mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True))
+ exchange = get_patched_exchange(mocker, whitelist_conf)
+ pm = PairListManager(exchange, whitelist_conf)
+ pm.refresh_pairlist()
+
+ assert log_has("PerformanceFilter is not available in this mode.", caplog)
+
+
def test_gen_pair_whitelist_not_supported(mocker, default_conf, tickers) -> None:
default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 10}]
@@ -787,6 +805,10 @@ def test_spreadfilter_invalid_data(mocker, default_conf, markets, tickers, caplo
"[{'PriceFilter': 'PriceFilter - Filtering pairs priced below 0.00002000.'}]",
None
),
+ ({"method": "PriceFilter", "max_value": 0.00002000},
+ "[{'PriceFilter': 'PriceFilter - Filtering pairs priced Value above 0.00002000.'}]",
+ None
+ ),
({"method": "PriceFilter"},
"[{'PriceFilter': 'PriceFilter - No price filters configured.'}]",
None
@@ -803,6 +825,10 @@ def test_spreadfilter_invalid_data(mocker, default_conf, markets, tickers, caplo
None,
"PriceFilter requires max_price to be >= 0"
), # OperationalException expected
+ ({"method": "PriceFilter", "max_value": -1.00010000},
+ None,
+ "PriceFilter requires max_value to be >= 0"
+ ), # OperationalException expected
({"method": "RangeStabilityFilter", "lookback_days": 10, "min_rate_of_change": 0.01},
"[{'RangeStabilityFilter': 'RangeStabilityFilter - Filtering pairs with rate of change below "
"0.01 over the last days.'}]",
diff --git a/tests/rpc/test_fiat_convert.py b/tests/rpc/test_fiat_convert.py
index 2d43addff..5174f9416 100644
--- a/tests/rpc/test_fiat_convert.py
+++ b/tests/rpc/test_fiat_convert.py
@@ -1,6 +1,7 @@
# pragma pylint: disable=missing-docstring, too-many-arguments, too-many-ancestors,
# pragma pylint: disable=protected-access, C0103
+import datetime
from unittest.mock import MagicMock
import pytest
@@ -21,6 +22,12 @@ def test_fiat_convert_is_supported(mocker):
def test_fiat_convert_find_price(mocker):
fiat_convert = CryptoToFiatConverter()
+ fiat_convert._cryptomap = {}
+ fiat_convert._backoff = 0
+ mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._load_cryptomap',
+ return_value=None)
+ assert fiat_convert.get_price(crypto_symbol='BTC', fiat_symbol='EUR') == 0.0
+
with pytest.raises(ValueError, match=r'The fiat ABC is not supported.'):
fiat_convert._find_price(crypto_symbol='BTC', fiat_symbol='ABC')
@@ -115,6 +122,28 @@ def test_fiat_convert_without_network(mocker):
CryptoToFiatConverter._coingekko = cmc_temp
+def test_fiat_too_many_requests_response(mocker, caplog):
+ # Because CryptoToFiatConverter is a Singleton we reset the listings
+ req_exception = "429 Too Many Requests"
+ listmock = MagicMock(return_value="{}", side_effect=RequestException(req_exception))
+ mocker.patch.multiple(
+ 'freqtrade.rpc.fiat_convert.CoinGeckoAPI',
+ get_coins_list=listmock,
+ )
+ # with pytest.raises(RequestEsxception):
+ fiat_convert = CryptoToFiatConverter()
+ fiat_convert._cryptomap = {}
+ fiat_convert._load_cryptomap()
+
+ length_cryptomap = len(fiat_convert._cryptomap)
+ assert length_cryptomap == 0
+ assert fiat_convert._backoff > datetime.datetime.now().timestamp()
+ assert log_has(
+ 'Too many requests for Coingecko API, backing off and trying again later.',
+ caplog
+ )
+
+
def test_fiat_invalid_response(mocker, caplog):
# Because CryptoToFiatConverter is a Singleton we reset the listings
listmock = MagicMock(return_value="{'novalidjson':DEADBEEFf}")
diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py
index 6d31e7635..e7a968e37 100644
--- a/tests/rpc/test_rpc.py
+++ b/tests/rpc/test_rpc.py
@@ -199,28 +199,31 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None:
freqtradebot.enter_positions()
- result, headers = rpc._rpc_status_table(default_conf['stake_currency'], 'USD')
+ result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf['stake_currency'], 'USD')
assert "Since" in headers
assert "Pair" in headers
assert 'instantly' == result[0][2]
assert 'ETH/BTC' in result[0][1]
assert '-0.41%' == result[0][3]
+ assert isnan(fiat_profit_sum)
# Test with fiatconvert
rpc._fiat_converter = CryptoToFiatConverter()
- result, headers = rpc._rpc_status_table(default_conf['stake_currency'], 'USD')
+ result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf['stake_currency'], 'USD')
assert "Since" in headers
assert "Pair" in headers
assert 'instantly' == result[0][2]
assert 'ETH/BTC' in result[0][1]
assert '-0.41% (-0.06)' == result[0][3]
+ assert '-0.06' == f'{fiat_profit_sum:.2f}'
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_sell_rate',
MagicMock(side_effect=ExchangeError("Pair 'ETH/BTC' not available")))
- result, headers = rpc._rpc_status_table(default_conf['stake_currency'], 'USD')
+ result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf['stake_currency'], 'USD')
assert 'instantly' == result[0][2]
assert 'ETH/BTC' in result[0][1]
assert 'nan%' == result[0][3]
+ assert isnan(fiat_profit_sum)
def test_rpc_daily_profit(default_conf, update, ticker, fee,
@@ -419,7 +422,7 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
assert stats['trade_count'] == 2
assert stats['first_trade_date'] == 'just now'
assert stats['latest_trade_date'] == 'just now'
- assert stats['avg_duration'] == '0:00:00'
+ assert stats['avg_duration'] in ('0:00:00', '0:00:01')
assert stats['best_pair'] == 'ETH/BTC'
assert prec_satoshi(stats['best_rate'], 6.2)
@@ -430,7 +433,7 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
assert stats['trade_count'] == 2
assert stats['first_trade_date'] == 'just now'
assert stats['latest_trade_date'] == 'just now'
- assert stats['avg_duration'] == '0:00:00'
+ assert stats['avg_duration'] in ('0:00:00', '0:00:01')
assert stats['best_pair'] == 'ETH/BTC'
assert prec_satoshi(stats['best_rate'], 6.2)
assert isnan(stats['profit_all_coin'])
diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py
index 69d312e65..1a66b2e81 100644
--- a/tests/rpc/test_rpc_apiserver.py
+++ b/tests/rpc/test_rpc_apiserver.py
@@ -710,7 +710,7 @@ def test_api_stats(botclient, mocker, ticker, fee, markets,):
assert 'draws' in rc.json()['durations']
-def test_api_performance(botclient, mocker, ticker, fee):
+def test_api_performance(botclient, fee):
ftbot, client = botclient
patch_get_signal(ftbot, (True, False))
@@ -728,6 +728,7 @@ def test_api_performance(botclient, mocker, ticker, fee):
)
trade.close_profit = trade.calc_profit_ratio()
+ trade.close_profit_abs = trade.calc_profit()
Trade.query.session.add(trade)
trade = Trade(
@@ -743,14 +744,16 @@ def test_api_performance(botclient, mocker, ticker, fee):
close_rate=0.391
)
trade.close_profit = trade.calc_profit_ratio()
+ trade.close_profit_abs = trade.calc_profit()
+
Trade.query.session.add(trade)
Trade.query.session.flush()
rc = client_get(client, f"{BASE_URI}/performance")
assert_response(rc)
assert len(rc.json()) == 2
- assert rc.json() == [{'count': 1, 'pair': 'LTC/ETH', 'profit': 7.61},
- {'count': 1, 'pair': 'XRP/ETH', 'profit': -5.57}]
+ assert rc.json() == [{'count': 1, 'pair': 'LTC/ETH', 'profit': 7.61, 'profit_abs': 0.01872279},
+ {'count': 1, 'pair': 'XRP/ETH', 'profit': -5.57, 'profit_abs': -0.1150375}]
def test_api_status(botclient, mocker, ticker, fee, markets):
@@ -1142,6 +1145,14 @@ def test_api_plot_config(botclient):
assert_response(rc)
assert rc.json() == ftbot.strategy.plot_config
assert isinstance(rc.json()['main_plot'], dict)
+ assert isinstance(rc.json()['subplots'], dict)
+
+ ftbot.strategy.plot_config = {'main_plot': {'sma': {}}}
+ rc = client_get(client, f"{BASE_URI}/plot_config")
+ assert_response(rc)
+
+ assert isinstance(rc.json()['main_plot'], dict)
+ assert isinstance(rc.json()['subplots'], dict)
def test_api_strategies(botclient):
diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py
index 6a36c12a7..e640f2dff 100644
--- a/tests/rpc/test_rpc_telegram.py
+++ b/tests/rpc/test_rpc_telegram.py
@@ -4,6 +4,7 @@
import re
from datetime import datetime
+from functools import reduce
from random import choice, randint
from string import ascii_uppercase
from unittest.mock import ANY, MagicMock
@@ -54,6 +55,14 @@ class DummyCls(Telegram):
raise Exception('test')
+def get_telegram_testobject_with_inline(mocker, default_conf, mock=True, ftbot=None):
+ inline_msg_mock = MagicMock()
+ telegram, ftbot, msg_mock = get_telegram_testobject(mocker, default_conf)
+ mocker.patch('freqtrade.rpc.telegram.Telegram._send_inline_msg', inline_msg_mock)
+
+ return telegram, ftbot, msg_mock, inline_msg_mock
+
+
def get_telegram_testobject(mocker, default_conf, mock=True, ftbot=None):
msg_mock = MagicMock()
if mock:
@@ -902,6 +911,33 @@ def test_forcebuy_handle_exception(default_conf, update, mocker) -> None:
assert msg_mock.call_args_list[0][0][0] == 'Forcebuy not enabled.'
+def test_forcebuy_no_pair(default_conf, update, mocker) -> None:
+ mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
+
+ fbuy_mock = MagicMock(return_value=None)
+ mocker.patch('freqtrade.rpc.RPC._rpc_forcebuy', fbuy_mock)
+
+ telegram, freqtradebot, _, inline_msg_mock = get_telegram_testobject_with_inline(mocker,
+ default_conf)
+ patch_get_signal(freqtradebot, (True, False))
+
+ context = MagicMock()
+ context.args = []
+ telegram._forcebuy(update=update, context=context)
+
+ assert fbuy_mock.call_count == 0
+ assert inline_msg_mock.call_count == 1
+ assert inline_msg_mock.call_args_list[0][0][0] == 'Which pair?'
+ assert inline_msg_mock.call_args_list[0][1]['callback_query_handler'] == 'forcebuy'
+ keyboard = inline_msg_mock.call_args_list[0][1]['keyboard']
+ assert reduce(lambda acc, x: acc + len(x), keyboard, 0) == 4
+ update = MagicMock()
+ update.callback_query = MagicMock()
+ update.callback_query.data = 'XRP/USDT'
+ telegram._forcebuy_inline(update, None)
+ assert fbuy_mock.call_count == 1
+
+
def test_performance_handle(default_conf, update, ticker, fee,
limit_buy_order, limit_sell_order, mocker) -> None:
@@ -929,7 +965,7 @@ def test_performance_handle(default_conf, update, ticker, fee,
telegram._performance(update=update, context=MagicMock())
assert msg_mock.call_count == 1
assert 'Performance' in msg_mock.call_args_list[0][0][0]
- assert 'ETH/BTC\t6.20% (1)
' in msg_mock.call_args_list[0][0][0]
+ assert 'ETH/BTC\t0.00006217 BTC (6.20%) (1)
' in msg_mock.call_args_list[0][0][0]
def test_count_handle(default_conf, update, ticker, fee, mocker) -> None:
@@ -969,6 +1005,11 @@ def test_telegram_lock_handle(default_conf, update, ticker, fee, mocker) -> None
)
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
patch_get_signal(freqtradebot, (True, False))
+ telegram._locks(update=update, context=MagicMock())
+ assert msg_mock.call_count == 1
+ assert 'No active locks.' in msg_mock.call_args_list[0][0][0]
+
+ msg_mock.reset_mock()
PairLocks.lock_pair('ETH/BTC', arrow.utcnow().shift(minutes=4).datetime, 'randreason')
PairLocks.lock_pair('XRP/BTC', arrow.utcnow().shift(minutes=20).datetime, 'deadbeef')
@@ -1102,6 +1143,15 @@ def test_edge_enabled(edge_conf, update, mocker) -> None:
assert 'Edge only validated following pairs:\n' in msg_mock.call_args_list[0][0][0]
assert 'Pair Winrate Expectancy Stoploss' in msg_mock.call_args_list[0][0][0]
+ msg_mock.reset_mock()
+
+ mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock(
+ return_value={}))
+ telegram._edge(update=update, context=MagicMock())
+ assert msg_mock.call_count == 1
+ assert 'Edge only validated following pairs:' in msg_mock.call_args_list[0][0][0]
+ assert 'Winrate' not in msg_mock.call_args_list[0][0][0]
+
def test_telegram_trades(mocker, update, default_conf, fee):
diff --git a/tests/strategy/test_default_strategy.py b/tests/strategy/test_default_strategy.py
index a8862e9c9..92ac9f63a 100644
--- a/tests/strategy/test_default_strategy.py
+++ b/tests/strategy/test_default_strategy.py
@@ -36,10 +36,11 @@ def test_default_strategy(result, fee):
)
assert strategy.confirm_trade_entry(pair='ETH/BTC', order_type='limit', amount=0.1,
- rate=20000, time_in_force='gtc') is True
+ rate=20000, time_in_force='gtc',
+ current_time=datetime.utcnow()) is True
assert strategy.confirm_trade_exit(pair='ETH/BTC', trade=trade, order_type='limit', amount=0.1,
- rate=20000, time_in_force='gtc', sell_reason='roi') is True
+ rate=20000, time_in_force='gtc', sell_reason='roi',
+ current_time=datetime.utcnow()) is True
assert strategy.custom_stoploss(pair='ETH/BTC', trade=trade, current_time=datetime.now(),
- current_rate=20_000, current_profit=0.05, dataframe=None
- ) == strategy.stoploss
+ current_rate=20_000, current_profit=0.05) == strategy.stoploss
diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py
index bd81bc80c..ded396779 100644
--- a/tests/strategy/test_interface.py
+++ b/tests/strategy/test_interface.py
@@ -360,7 +360,7 @@ def test_stop_loss_reached(default_conf, fee, profit, adjusted, expected, traili
now = arrow.utcnow().datetime
sl_flag = strategy.stop_loss_reached(current_rate=trade.open_rate * (1 + profit), trade=trade,
current_time=now, current_profit=profit,
- force_stoploss=0, high=None, dataframe=None)
+ force_stoploss=0, high=None)
assert isinstance(sl_flag, SellCheckTuple)
assert sl_flag.sell_type == expected
if expected == SellType.NONE:
@@ -371,7 +371,7 @@ def test_stop_loss_reached(default_conf, fee, profit, adjusted, expected, traili
sl_flag = strategy.stop_loss_reached(current_rate=trade.open_rate * (1 + profit2), trade=trade,
current_time=now, current_profit=profit2,
- force_stoploss=0, high=None, dataframe=None)
+ force_stoploss=0, high=None)
assert sl_flag.sell_type == expected2
if expected2 == SellType.NONE:
assert sl_flag.sell_flag is False
@@ -399,27 +399,27 @@ def test_custom_sell(default_conf, fee, caplog) -> None:
)
now = arrow.utcnow().datetime
- res = strategy.should_sell(None, trade, 1, now, False, False, None, None, 0)
+ res = strategy.should_sell(trade, 1, now, False, False, None, None, 0)
assert res.sell_flag is False
assert res.sell_type == SellType.NONE
strategy.custom_sell = MagicMock(return_value=True)
- res = strategy.should_sell(None, trade, 1, now, False, False, None, None, 0)
+ res = strategy.should_sell(trade, 1, now, False, False, None, None, 0)
assert res.sell_flag is True
assert res.sell_type == SellType.CUSTOM_SELL
assert res.sell_reason == 'custom_sell'
strategy.custom_sell = MagicMock(return_value='hello world')
- res = strategy.should_sell(None, trade, 1, now, False, False, None, None, 0)
+ res = strategy.should_sell(trade, 1, now, False, False, None, None, 0)
assert res.sell_type == SellType.CUSTOM_SELL
assert res.sell_flag is True
assert res.sell_reason == 'hello world'
caplog.clear()
strategy.custom_sell = MagicMock(return_value='h' * 100)
- res = strategy.should_sell(None, trade, 1, now, False, False, None, None, 0)
+ res = strategy.should_sell(trade, 1, now, False, False, None, None, 0)
assert res.sell_type == SellType.CUSTOM_SELL
assert res.sell_flag is True
assert res.sell_reason == 'h' * 64
@@ -636,7 +636,7 @@ def test_hyperopt_parameters():
assert len(list(intpar.range)) == 1
# Range contains ONLY the default / value.
assert list(intpar.range) == [intpar.value]
- intpar.hyperopt = True
+ intpar.in_space = True
assert len(list(intpar.range)) == 6
assert list(intpar.range) == [0, 1, 2, 3, 4, 5]
@@ -671,4 +671,4 @@ def test_auto_hyperopt_interface(default_conf):
strategy.sell_rsi = IntParameter([0, 10], default=5, space='buy')
with pytest.raises(OperationalException, match=r"Inconclusive parameter.*"):
- [x for x in strategy.enumerate_parameters('sell')]
+ [x for x in strategy._detect_parameters('sell')]
diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py
index 785e866ae..4d9284a2f 100644
--- a/tests/test_freqtradebot.py
+++ b/tests/test_freqtradebot.py
@@ -1371,7 +1371,8 @@ def test_handle_stoploss_on_exchange_trailing_error(mocker, default_conf, fee, c
}
mocker.patch('freqtrade.exchange.Binance.cancel_stoploss_order',
side_effect=InvalidOrderException())
- mocker.patch('freqtrade.exchange.Binance.fetch_stoploss_order', stoploss_order_hanging)
+ mocker.patch('freqtrade.exchange.Binance.fetch_stoploss_order',
+ return_value=stoploss_order_hanging)
freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging)
assert log_has_re(r"Could not cancel stoploss order abcd for pair ETH/BTC.*", caplog)
@@ -2430,13 +2431,22 @@ def test_handle_cancel_buy(mocker, caplog, default_conf, limit_buy_order) -> Non
freqtrade._notify_buy_cancel = MagicMock()
trade = MagicMock()
- trade.pair = 'LTC/ETH'
+ trade.pair = 'LTC/USDT'
+ trade.open_rate = 200
limit_buy_order['filled'] = 0.0
limit_buy_order['status'] = 'open'
reason = CANCEL_REASON['TIMEOUT']
assert freqtrade.handle_cancel_buy(trade, limit_buy_order, reason)
assert cancel_order_mock.call_count == 1
+ cancel_order_mock.reset_mock()
+ caplog.clear()
+ limit_buy_order['filled'] = 0.01
+ assert not freqtrade.handle_cancel_buy(trade, limit_buy_order, reason)
+ assert cancel_order_mock.call_count == 0
+ assert log_has_re("Order .* for .* not cancelled, as the filled amount.* unsellable.*", caplog)
+
+ caplog.clear()
cancel_order_mock.reset_mock()
limit_buy_order['filled'] = 2
assert not freqtrade.handle_cancel_buy(trade, limit_buy_order, reason)
@@ -2491,7 +2501,8 @@ def test_handle_cancel_buy_corder_empty(mocker, default_conf, limit_buy_order,
freqtrade._notify_buy_cancel = MagicMock()
trade = MagicMock()
- trade.pair = 'LTC/ETH'
+ trade.pair = 'LTC/USDT'
+ trade.open_rate = 200
limit_buy_order['filled'] = 0.0
limit_buy_order['status'] = 'open'
reason = CANCEL_REASON['TIMEOUT']
diff --git a/tests/test_integration.py b/tests/test_integration.py
index 217910961..33b3e1140 100644
--- a/tests/test_integration.py
+++ b/tests/test_integration.py
@@ -63,7 +63,7 @@ def test_may_execute_sell_stoploss_on_exchange_multi(default_conf, ticker, fee,
amount_to_precision=lambda s, x, y: y,
price_to_precision=lambda s, x, y: y,
fetch_stoploss_order=stoploss_order_mock,
- cancel_stoploss_order=cancel_order_mock,
+ cancel_stoploss_order_with_result=cancel_order_mock,
)
mocker.patch.multiple(
diff --git a/tests/test_persistence.py b/tests/test_persistence.py
index 8470e12c2..669f220bb 100644
--- a/tests/test_persistence.py
+++ b/tests/test_persistence.py
@@ -7,7 +7,7 @@ from unittest.mock import MagicMock
import arrow
import pytest
-from sqlalchemy import create_engine
+from sqlalchemy import create_engine, inspect
from freqtrade import constants
from freqtrade.exceptions import DependencyException, OperationalException
@@ -627,6 +627,63 @@ def test_migrate_new(mocker, default_conf, fee, caplog):
assert orders[1].order_id == 'stop_order_id222'
assert orders[1].ft_order_side == 'stoploss'
+ caplog.clear()
+ # Drop latest column
+ engine.execute("alter table orders rename to orders_bak")
+ inspector = inspect(engine)
+
+ for index in inspector.get_indexes('orders_bak'):
+ engine.execute(f"drop index {index['name']}")
+ # Recreate table
+ engine.execute("""
+ CREATE TABLE orders (
+ id INTEGER NOT NULL,
+ ft_trade_id INTEGER,
+ ft_order_side VARCHAR NOT NULL,
+ ft_pair VARCHAR NOT NULL,
+ ft_is_open BOOLEAN NOT NULL,
+ order_id VARCHAR NOT NULL,
+ status VARCHAR,
+ symbol VARCHAR,
+ order_type VARCHAR,
+ side VARCHAR,
+ price FLOAT,
+ amount FLOAT,
+ filled FLOAT,
+ remaining FLOAT,
+ cost FLOAT,
+ order_date DATETIME,
+ order_filled_date DATETIME,
+ order_update_date DATETIME,
+ PRIMARY KEY (id),
+ CONSTRAINT _order_pair_order_id UNIQUE (ft_pair, order_id),
+ FOREIGN KEY(ft_trade_id) REFERENCES trades (id)
+ )
+ """)
+
+ engine.execute("""
+ insert into orders ( id, ft_trade_id, ft_order_side, ft_pair, ft_is_open, order_id, status,
+ symbol, order_type, side, price, amount, filled, remaining, cost, order_date,
+ order_filled_date, order_update_date)
+ select id, ft_trade_id, ft_order_side, ft_pair, ft_is_open, order_id, status,
+ symbol, order_type, side, price, amount, filled, remaining, cost, order_date,
+ order_filled_date, order_update_date
+ from orders_bak
+ """)
+
+ # Run init to test migration
+ init_db(default_conf['db_url'], default_conf['dry_run'])
+
+ assert log_has("trying orders_bak1", caplog)
+
+ orders = Order.query.all()
+ assert len(orders) == 2
+ assert orders[0].order_id == 'buy_order'
+ assert orders[0].ft_order_side == 'buy'
+
+ assert orders[1].order_id == 'stop_order_id222'
+ assert orders[1].ft_order_side == 'stoploss'
+
def test_migrate_mid_state(mocker, default_conf, fee, caplog):
"""
diff --git a/tests/testdata/hyperopt_results_SampleStrategy.pickle b/tests/testdata/hyperopt_results_SampleStrategy.pickle
new file mode 100644
index 000000000..2231de7bf
Binary files /dev/null and b/tests/testdata/hyperopt_results_SampleStrategy.pickle differ
diff --git a/tests/testdata/strategy_SampleStrategy.fthypt b/tests/testdata/strategy_SampleStrategy.fthypt
new file mode 100644
index 000000000..6dc2b9ab1
--- /dev/null
+++ b/tests/testdata/strategy_SampleStrategy.fthypt
@@ -0,0 +1,5 @@
+{"loss":100000,"params_dict":{"mfi-value":"20","fastd-value":"21","adx-value":"26","rsi-value":"23","mfi-enabled":true,"fastd-enabled":false,"adx-enabled":false,"rsi-enabled":true,"trigger":"sar_reversal","sell-mfi-value":"97","sell-fastd-value":"85","sell-adx-value":"55","sell-rsi-value":"76","sell-mfi-enabled":true,"sell-fastd-enabled":false,"sell-adx-enabled":true,"sell-rsi-enabled":true,"sell-trigger":"sell-bb_upper","roi_t1":"34","roi_t2":"28","roi_t3":"32","roi_p1":0.031,"roi_p2":0.033,"roi_p3":0.146,"stoploss":-0.05},"params_details":{"buy":{"mfi-value":"20","fastd-value":"21","adx-value":"26","rsi-value":"23","mfi-enabled":true,"fastd-enabled":false,"adx-enabled":false,"rsi-enabled":true,"trigger":"sar_reversal"},"sell":{"sell-mfi-value":"97","sell-fastd-value":"85","sell-adx-value":"55","sell-rsi-value":"76","sell-mfi-enabled":true,"sell-fastd-enabled":false,"sell-adx-enabled":true,"sell-rsi-enabled":true,"sell-trigger":"sell-bb_upper"},"roi":"{0: 0.21, 32: 0.064, 60: 0.031, 94: 0}","stoploss":{"stoploss":-0.05}},"params_not_optimized":{"buy":{},"sell":{}},"results_metrics":{"trades":[],"locks":[],"best_pair":{"key":"ETH/BTC","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0,"profit_sum_pct":0.0,"profit_total_abs":0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0},"worst_pair":{"key":"ETH/BTC","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0,"profit_sum_pct":0.0,"profit_total_abs":0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0},"results_per_pair":[{"key":"ETH/BTC","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0,"profit_sum_pct":0.0,"profit_total_abs":0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0},{"key":"LTC/BTC","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0,"profit_sum_pct":0.0,"profit_total_abs":0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0},{"key":"ETC/BTC","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0,"profit_sum_pct":0.0,"profit_total_abs":0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0},{"key":"XLM/BTC","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0,"profit_sum_pct":0.0,"profit_total_abs":0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0},{"key":"TRX/BTC","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0,"profit_sum_pct":0.0,"profit_total_abs":0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0},{"key":"ADA/BTC","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0,"profit_sum_pct":0.0,"profit_total_abs":0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0},{"key":"TOTAL","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0,"profit_sum_pct":0.0,"profit_total_abs":0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0}],"sell_reason_summary":[],"left_open_trades":[{"key":"TOTAL","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0,"profit_sum_pct":0.0,"profit_total_abs":0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0}],"total_trades":0,"total_volume":0.0,"avg_stake_amount":0,"profit_mean":0,"profit_median":0,"profit_total":0.0,"profit_total_abs":0,"backtest_start":"2018-01-10 07:25:00","backtest_start_ts":1515569100000,"backtest_end":"2018-01-30 04:45:00","backtest_end_ts":1517287500000,"backtest_days":19,"backtest_run_start_ts":1620793107,"backtest_run_end_ts":1620793107,"trades_per_day":0.0,"market_change":0,"pairlist":["ETH/BTC","LTC/BTC","ETC/BTC","XLM/BTC","TRX/BTC","ADA/BTC"],"stake_amount":0.05,"stake_currency":"BTC","stake_currency_decimals":8,"starting_balance":1000,"dry_run_wallet":1000,"final_balance":1000,"max_open_trades":3,"max_open_trades_setting":3,"timeframe":"5m","timerange":"","enable_protections":false,"strategy_name":"SampleStrategy","stoploss":-0.1,"trailing_stop":false,"trailing_stop_positive":null,"trailing_stop_positive_offset":0.0,"trailing_only_offset_is_reached":false,"use_custom_stoploss":false,"minimal_roi":{"60":0.01,"30":0.02,"0":0.04},"use_sell_signal":true,"sell_profit_only":false,"sell_profit_offset":0.0,"ignore_roi_if_buy_signal":false,"backtest_best_day":0,"backtest_worst_day":0,"backtest_best_day_abs":0,"backtest_worst_day_abs":0,"winning_days":0,"draw_days":0,"losing_days":0,"wins":0,"losses":0,"draws":0,"holding_avg":"0:00:00","winner_holding_avg":"0:00:00","loser_holding_avg":"0:00:00","max_drawdown":0.0,"max_drawdown_abs":0.0,"max_drawdown_low":0.0,"max_drawdown_high":0.0,"drawdown_start":"1970-01-01 00:00:00+00:00","drawdown_start_ts":0,"drawdown_end":"1970-01-01 00:00:00+00:00","drawdown_end_ts":0,"csum_min":0,"csum_max":0},"results_explanation":" 0 trades. 0/0/0 Wins/Draws/Losses. Avg profit 0.00%. Median profit 0.00%. Total profit 0.00000000 BTC ( 0.00\u03A3%). Avg duration 0:00:00 min.","total_profit":0.0,"current_epoch":1,"is_initial_point":true,"is_best":false}
+{"loss":100000,"params_dict":{"mfi-value":"14","fastd-value":"43","adx-value":"30","rsi-value":"24","mfi-enabled":true,"fastd-enabled":true,"adx-enabled":false,"rsi-enabled":true,"trigger":"sar_reversal","sell-mfi-value":"97","sell-fastd-value":"71","sell-adx-value":"82","sell-rsi-value":"99","sell-mfi-enabled":false,"sell-fastd-enabled":false,"sell-adx-enabled":false,"sell-rsi-enabled":true,"sell-trigger":"sell-bb_upper","roi_t1":"84","roi_t2":"35","roi_t3":"19","roi_p1":0.024,"roi_p2":0.022,"roi_p3":0.061,"stoploss":-0.083},"params_details":{"buy":{"mfi-value":"14","fastd-value":"43","adx-value":"30","rsi-value":"24","mfi-enabled":true,"fastd-enabled":true,"adx-enabled":false,"rsi-enabled":true,"trigger":"sar_reversal"},"sell":{"sell-mfi-value":"97","sell-fastd-value":"71","sell-adx-value":"82","sell-rsi-value":"99","sell-mfi-enabled":false,"sell-fastd-enabled":false,"sell-adx-enabled":false,"sell-rsi-enabled":true,"sell-trigger":"sell-bb_upper"},"roi":"{0: 0.107, 19: 0.046, 54: 0.024, 138: 0}","stoploss":{"stoploss":-0.083}},"params_not_optimized":{"buy":{},"sell":{}},"results_metrics":{"trades":[],"locks":[],"best_pair":{"key":"ETH/BTC","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0,"profit_sum_pct":0.0,"profit_total_abs":0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0},"worst_pair":{"key":"ETH/BTC","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0,"profit_sum_pct":0.0,"profit_total_abs":0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0},"results_per_pair":[{"key":"ETH/BTC","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0,"profit_sum_pct":0.0,"profit_total_abs":0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0},{"key":"LTC/BTC","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0,"profit_sum_pct":0.0,"profit_total_abs":0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0},{"key":"ETC/BTC","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0,"profit_sum_pct":0.0,"profit_total_abs":0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0},{"key":"XLM/BTC","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0,"profit_sum_pct":0.0,"profit_total_abs":0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0},{"key":"TRX/BTC","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0,"profit_sum_pct":0.0,"profit_total_abs":0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0},{"key":"ADA/BTC","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0,"profit_sum_pct":0.0,"profit_total_abs":0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0},{"key":"TOTAL","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0,"profit_sum_pct":0.0,"profit_total_abs":0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0}],"sell_reason_summary":[],"left_open_trades":[{"key":"TOTAL","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0,"profit_sum_pct":0.0,"profit_total_abs":0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0}],"total_trades":0,"total_volume":0.0,"avg_stake_amount":0,"profit_mean":0,"profit_median":0,"profit_total":0.0,"profit_total_abs":0,"backtest_start":"2018-01-10 07:25:00","backtest_start_ts":1515569100000,"backtest_end":"2018-01-30 04:45:00","backtest_end_ts":1517287500000,"backtest_days":19,"backtest_run_start_ts":1620793107,"backtest_run_end_ts":1620793108,"trades_per_day":0.0,"market_change":0,"pairlist":["ETH/BTC","LTC/BTC","ETC/BTC","XLM/BTC","TRX/BTC","ADA/BTC"],"stake_amount":0.05,"stake_currency":"BTC","stake_currency_decimals":8,"starting_balance":1000,"dry_run_wallet":1000,"final_balance":1000,"max_open_trades":3,"max_open_trades_setting":3,"timeframe":"5m","timerange":"","enable_protections":false,"strategy_name":"SampleStrategy","stoploss":-0.1,"trailing_stop":false,"trailing_stop_positive":null,"trailing_stop_positive_offset":0.0,"trailing_only_offset_is_reached":false,"use_custom_stoploss":false,"minimal_roi":{"60":0.01,"30":0.02,"0":0.04},"use_sell_signal":true,"sell_profit_only":false,"sell_profit_offset":0.0,"ignore_roi_if_buy_signal":false,"backtest_best_day":0,"backtest_worst_day":0,"backtest_best_day_abs":0,"backtest_worst_day_abs":0,"winning_days":0,"draw_days":0,"losing_days":0,"wins":0,"losses":0,"draws":0,"holding_avg":"0:00:00","winner_holding_avg":"0:00:00","loser_holding_avg":"0:00:00","max_drawdown":0.0,"max_drawdown_abs":0.0,"max_drawdown_low":0.0,"max_drawdown_high":0.0,"drawdown_start":"1970-01-01 00:00:00+00:00","drawdown_start_ts":0,"drawdown_end":"1970-01-01 00:00:00+00:00","drawdown_end_ts":0,"csum_min":0,"csum_max":0},"results_explanation":" 0 trades. 0/0/0 Wins/Draws/Losses. Avg profit 0.00%. Median profit 0.00%. Total profit 0.00000000 BTC ( 0.00\u03A3%). Avg duration 0:00:00 min.","total_profit":0.0,"current_epoch":2,"is_initial_point":true,"is_best":false}
+{"loss":2.183447401951895,"params_dict":{"mfi-value":"14","fastd-value":"15","adx-value":"40","rsi-value":"36","mfi-enabled":false,"fastd-enabled":true,"adx-enabled":false,"rsi-enabled":false,"trigger":"sar_reversal","sell-mfi-value":"92","sell-fastd-value":"84","sell-adx-value":"61","sell-rsi-value":"61","sell-mfi-enabled":true,"sell-fastd-enabled":true,"sell-adx-enabled":true,"sell-rsi-enabled":true,"sell-trigger":"sell-bb_upper","roi_t1":"68","roi_t2":"41","roi_t3":"21","roi_p1":0.015,"roi_p2":0.064,"roi_p3":0.126,"stoploss":-0.024},"params_details":{"buy":{"mfi-value":"14","fastd-value":"15","adx-value":"40","rsi-value":"36","mfi-enabled":false,"fastd-enabled":true,"adx-enabled":false,"rsi-enabled":false,"trigger":"sar_reversal"},"sell":{"sell-mfi-value":"92","sell-fastd-value":"84","sell-adx-value":"61","sell-rsi-value":"61","sell-mfi-enabled":true,"sell-fastd-enabled":true,"sell-adx-enabled":true,"sell-rsi-enabled":true,"sell-trigger":"sell-bb_upper"},"roi":"{0: 0.20500000000000002, 21: 0.079, 62: 0.015, 130: 0}","stoploss":{"stoploss":-0.024}},"params_not_optimized":{"buy":{},"sell":{}},"results_metrics":{"trades":[{"pair":"LTC/BTC","stake_amount":0.05,"amount":2.94115571,"open_date":"2018-01-11 11:40:00+00:00","close_date":"2018-01-11 19:40:00+00:00","open_rate":0.01700012,"close_rate":0.017119538805820372,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":480,"profit_ratio":0.0,"profit_abs":0.0,"sell_reason":"roi","initial_stop_loss_abs":0.01659211712,"initial_stop_loss_ratio":-0.024,"stop_loss_abs":0.01659211712,"stop_loss_ratio":-0.024,"min_rate":0.01689809,"max_rate":0.0171462,"is_open":false,"open_timestamp":1515670800000.0,"close_timestamp":1515699600000.0},{"pair":"ETH/BTC","stake_amount":0.05,"amount":0.57407318,"open_date":"2018-01-12 11:05:00+00:00","close_date":"2018-01-12 12:30:00+00:00","open_rate":0.08709691,"close_rate":0.08901977203712995,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":85,"profit_ratio":0.01494768,"profit_abs":0.00075,"sell_reason":"roi","initial_stop_loss_abs":0.08500658416,"initial_stop_loss_ratio":-0.024,"stop_loss_abs":0.08500658416,"stop_loss_ratio":-0.024,"min_rate":0.08702974000000001,"max_rate":0.08929248000000001,"is_open":false,"open_timestamp":1515755100000.0,"close_timestamp":1515760200000.0},{"pair":"LTC/BTC","stake_amount":0.05,"amount":2.93166236,"open_date":"2018-01-12 03:30:00+00:00","close_date":"2018-01-12 13:05:00+00:00","open_rate":0.01705517,"close_rate":0.01717497550928249,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":575,"profit_ratio":0.0,"profit_abs":0.0,"sell_reason":"roi","initial_stop_loss_abs":0.016645845920000003,"initial_stop_loss_ratio":-0.024,"stop_loss_abs":0.016645845920000003,"stop_loss_ratio":-0.024,"min_rate":0.0169841,"max_rate":0.01719135,"is_open":false,"open_timestamp":1515727800000.0,"close_timestamp":1515762300000.0},{"pair":"LTC/BTC","stake_amount":0.05,"amount":2.96876855,"open_date":"2018-01-13 03:50:00+00:00","close_date":"2018-01-13 06:05:00+00:00","open_rate":0.016842,"close_rate":0.016960308078273957,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":135,"profit_ratio":0.0,"profit_abs":0.0,"sell_reason":"roi","initial_stop_loss_abs":0.016437792,"initial_stop_loss_ratio":-0.024,"stop_loss_abs":0.016437792,"stop_loss_ratio":-0.024,"min_rate":0.016836999999999998,"max_rate":0.017,"is_open":false,"open_timestamp":1515815400000.0,"close_timestamp":1515823500000.0},{"pair":"ETH/BTC","stake_amount":0.05,"amount":0.53163205,"open_date":"2018-01-13 13:25:00+00:00","close_date":"2018-01-13 15:35:00+00:00","open_rate":0.09405001,"close_rate":0.09471067238835926,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":130,"profit_ratio":0.0,"profit_abs":0.0,"sell_reason":"roi","initial_stop_loss_abs":0.09179280976,"initial_stop_loss_ratio":-0.024,"stop_loss_abs":0.09179280976,"stop_loss_ratio":-0.024,"min_rate":0.09369894000000001,"max_rate":0.09479997,"is_open":false,"open_timestamp":1515849900000.0,"close_timestamp":1515857700000.0},{"pair":"ETC/BTC","stake_amount":0.05,"amount":19.23816853,"open_date":"2018-01-13 15:30:00+00:00","close_date":"2018-01-13 16:20:00+00:00","open_rate":0.0025989999999999997,"close_rate":0.0028232990466633217,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":50,"profit_ratio":0.07872446,"profit_abs":0.00395,"sell_reason":"roi","initial_stop_loss_abs":0.002536624,"initial_stop_loss_ratio":-0.024,"stop_loss_abs":0.002536624,"stop_loss_ratio":-0.024,"min_rate":0.00259525,"max_rate":0.0028288700000000003,"is_open":false,"open_timestamp":1515857400000.0,"close_timestamp":1515860400000.0},{"pair":"TRX/BTC","stake_amount":0.05,"amount":492.80504632,"open_date":"2018-01-14 21:35:00+00:00","close_date":"2018-01-14 23:15:00+00:00","open_rate":0.00010146000000000001,"close_rate":0.00010369995985950828,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":100,"profit_ratio":0.01494768,"profit_abs":0.00075,"sell_reason":"roi","initial_stop_loss_abs":9.902496e-05,"initial_stop_loss_ratio":-0.024,"stop_loss_abs":9.902496e-05,"stop_loss_ratio":-0.024,"min_rate":0.0001012,"max_rate":0.00010414,"is_open":false,"open_timestamp":1515965700000.0,"close_timestamp":1515971700000.0},{"pair":"LTC/BTC","stake_amount":0.05,"amount":2.92398174,"open_date":"2018-01-15 12:45:00+00:00","close_date":"2018-01-15 21:05:00+00:00","open_rate":0.01709997,"close_rate":0.01722009021073758,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":500,"profit_ratio":0.0,"profit_abs":0.0,"sell_reason":"roi","initial_stop_loss_abs":0.016689570719999998,"initial_stop_loss_ratio":-0.024,"stop_loss_abs":0.016689570719999998,"stop_loss_ratio":-0.024,"min_rate":0.01694,"max_rate":0.01725,"is_open":false,"open_timestamp":1516020300000.0,"close_timestamp":1516050300000.0},{"pair":"XLM/BTC","stake_amount":0.05,"amount":1111.60515785,"open_date":"2018-01-15 19:50:00+00:00","close_date":"2018-01-15 23:45:00+00:00","open_rate":4.4980000000000006e-05,"close_rate":4.390048e-05,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":235,"profit_ratio":-0.03080817,"profit_abs":-0.0015458,"sell_reason":"stop_loss","initial_stop_loss_abs":4.390048e-05,"initial_stop_loss_ratio":-0.024,"stop_loss_abs":4.390048e-05,"stop_loss_ratio":-0.024,"min_rate":4.409e-05,"max_rate":4.502e-05,"is_open":false,"open_timestamp":1516045800000.0,"close_timestamp":1516059900000.0},{"pair":"TRX/BTC","stake_amount":0.05,"amount":519.80455349,"open_date":"2018-01-21 03:55:00+00:00","close_date":"2018-01-21 04:05:00+00:00","open_rate":9.619e-05,"close_rate":9.388144e-05,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":10,"profit_ratio":-0.03080817,"profit_abs":-0.0015458,"sell_reason":"stop_loss","initial_stop_loss_abs":9.388144e-05,"initial_stop_loss_ratio":-0.024,"stop_loss_abs":9.388144e-05,"stop_loss_ratio":-0.024,"min_rate":9.568e-05,"max_rate":9.673e-05,"is_open":false,"open_timestamp":1516506900000.0,"close_timestamp":1516507500000.0},{"pair":"LTC/BTC","stake_amount":0.05,"amount":3.029754,"open_date":"2018-01-20 22:15:00+00:00","close_date":"2018-01-21 07:45:00+00:00","open_rate":0.01650299,"close_rate":0.016106918239999997,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":570,"profit_ratio":-0.03080817,"profit_abs":-0.0015458,"sell_reason":"stop_loss","initial_stop_loss_abs":0.016106918239999997,"initial_stop_loss_ratio":-0.024,"stop_loss_abs":0.016106918239999997,"stop_loss_ratio":-0.024,"min_rate":0.0162468,"max_rate":0.01663179,"is_open":false,"open_timestamp":1516486500000.0,"close_timestamp":1516520700000.0},{"pair":"ETC/BTC","stake_amount":0.05,"amount":18.75461832,"open_date":"2018-01-21 13:00:00+00:00","close_date":"2018-01-21 16:25:00+00:00","open_rate":0.00266601,"close_rate":0.00260202576,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":205,"profit_ratio":-0.03080817,"profit_abs":-0.0015458,"sell_reason":"stop_loss","initial_stop_loss_abs":0.00260202576,"initial_stop_loss_ratio":-0.024,"stop_loss_abs":0.00260202576,"stop_loss_ratio":-0.024,"min_rate":0.0026290800000000002,"max_rate":0.00269384,"is_open":false,"open_timestamp":1516539600000.0,"close_timestamp":1516551900000.0},{"pair":"TRX/BTC","stake_amount":0.05,"amount":552.18111541,"open_date":"2018-01-22 02:10:00+00:00","close_date":"2018-01-22 04:20:00+00:00","open_rate":9.055e-05,"close_rate":9.118607626693427e-05,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":130,"profit_ratio":0.0,"profit_abs":0.0,"sell_reason":"roi","initial_stop_loss_abs":8.83768e-05,"initial_stop_loss_ratio":-0.024,"stop_loss_abs":8.83768e-05,"stop_loss_ratio":-0.024,"min_rate":9.013e-05,"max_rate":9.197e-05,"is_open":false,"open_timestamp":1516587000000.0,"close_timestamp":1516594800000.0},{"pair":"LTC/BTC","stake_amount":0.05,"amount":2.99733237,"open_date":"2018-01-22 03:20:00+00:00","close_date":"2018-01-22 13:50:00+00:00","open_rate":0.0166815,"close_rate":0.016281143999999997,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":630,"profit_ratio":-0.03080817,"profit_abs":-0.0015458,"sell_reason":"stop_loss","initial_stop_loss_abs":0.016281143999999997,"initial_stop_loss_ratio":-0.024,"stop_loss_abs":0.016281143999999997,"stop_loss_ratio":-0.024,"min_rate":0.01641443,"max_rate":0.016800000000000002,"is_open":false,"open_timestamp":1516591200000.0,"close_timestamp":1516629000000.0},{"pair":"TRX/BTC","stake_amount":0.05,"amount":503.52467271,"open_date":"2018-01-23 08:55:00+00:00","close_date":"2018-01-23 09:40:00+00:00","open_rate":9.93e-05,"close_rate":9.69168e-05,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":45,"profit_ratio":-0.03080817,"profit_abs":-0.0015458,"sell_reason":"stop_loss","initial_stop_loss_abs":9.69168e-05,"initial_stop_loss_ratio":-0.024,"stop_loss_abs":9.69168e-05,"stop_loss_ratio":-0.024,"min_rate":9.754e-05,"max_rate":0.00010025,"is_open":false,"open_timestamp":1516697700000.0,"close_timestamp":1516700400000.0},{"pair":"ETH/BTC","stake_amount":0.05,"amount":0.55148073,"open_date":"2018-01-24 02:10:00+00:00","close_date":"2018-01-24 04:40:00+00:00","open_rate":0.090665,"close_rate":0.09130188409433015,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":150,"profit_ratio":0.0,"profit_abs":0.0,"sell_reason":"roi","initial_stop_loss_abs":0.08848903999999999,"initial_stop_loss_ratio":-0.024,"stop_loss_abs":0.08848903999999999,"stop_loss_ratio":-0.024,"min_rate":0.090665,"max_rate":0.09146000000000001,"is_open":false,"open_timestamp":1516759800000.0,"close_timestamp":1516768800000.0},{"pair":"ETC/BTC","stake_amount":0.05,"amount":19.10584639,"open_date":"2018-01-24 19:20:00+00:00","close_date":"2018-01-24 21:35:00+00:00","open_rate":0.002617,"close_rate":0.0026353833416959357,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":135,"profit_ratio":0.0,"profit_abs":0.0,"sell_reason":"roi","initial_stop_loss_abs":0.002554192,"initial_stop_loss_ratio":-0.024,"stop_loss_abs":0.002554192,"stop_loss_ratio":-0.024,"min_rate":0.002617,"max_rate":0.00264999,"is_open":false,"open_timestamp":1516821600000.0,"close_timestamp":1516829700000.0},{"pair":"ETC/BTC","stake_amount":0.05,"amount":19.34602691,"open_date":"2018-01-25 14:35:00+00:00","close_date":"2018-01-25 16:35:00+00:00","open_rate":0.00258451,"close_rate":0.002641568926241846,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":120,"profit_ratio":0.01494768,"profit_abs":0.00075,"sell_reason":"roi","initial_stop_loss_abs":0.00252248176,"initial_stop_loss_ratio":-0.024,"stop_loss_abs":0.00252248176,"stop_loss_ratio":-0.024,"min_rate":0.00258451,"max_rate":0.00264579,"is_open":false,"open_timestamp":1516890900000.0,"close_timestamp":1516898100000.0},{"pair":"LTC/BTC","stake_amount":0.05,"amount":3.11910295,"open_date":"2018-01-24 23:05:00+00:00","close_date":"2018-01-25 16:55:00+00:00","open_rate":0.01603025,"close_rate":0.016142855870546913,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":1070,"profit_ratio":0.0,"profit_abs":0.0,"sell_reason":"roi","initial_stop_loss_abs":0.015645523999999997,"initial_stop_loss_ratio":-0.024,"stop_loss_abs":0.015645523999999997,"stop_loss_ratio":-0.024,"min_rate":0.015798760000000002,"max_rate":0.01617,"is_open":false,"open_timestamp":1516835100000.0,"close_timestamp":1516899300000.0},{"pair":"TRX/BTC","stake_amount":0.05,"amount":553.70985604,"open_date":"2018-01-26 19:30:00+00:00","close_date":"2018-01-26 23:30:00+00:00","open_rate":9.03e-05,"close_rate":9.093432012042147e-05,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":240,"profit_ratio":0.0,"profit_abs":0.0,"sell_reason":"roi","initial_stop_loss_abs":8.813279999999999e-05,"initial_stop_loss_ratio":-0.024,"stop_loss_abs":8.813279999999999e-05,"stop_loss_ratio":-0.024,"min_rate":8.961e-05,"max_rate":9.1e-05,"is_open":false,"open_timestamp":1516995000000.0,"close_timestamp":1517009400000.0},{"pair":"ETC/BTC","stake_amount":0.05,"amount":19.22929005,"open_date":"2018-01-26 21:15:00+00:00","close_date":"2018-01-28 03:50:00+00:00","open_rate":0.0026002,"close_rate":0.0026184653286502758,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":1835,"profit_ratio":0.0,"profit_abs":0.0,"sell_reason":"roi","initial_stop_loss_abs":0.0025377952,"initial_stop_loss_ratio":-0.024,"stop_loss_abs":0.0025377952,"stop_loss_ratio":-0.024,"min_rate":0.00254702,"max_rate":0.00262797,"is_open":false,"open_timestamp":1517001300000.0,"close_timestamp":1517111400000.0},{"pair":"LTC/BTC","stake_amount":0.05,"amount":3.15677093,"open_date":"2018-01-27 22:05:00+00:00","close_date":"2018-01-28 10:45:00+00:00","open_rate":0.01583897,"close_rate":0.015950232207727046,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":760,"profit_ratio":0.0,"profit_abs":0.0,"sell_reason":"roi","initial_stop_loss_abs":0.01545883472,"initial_stop_loss_ratio":-0.024,"stop_loss_abs":0.01545883472,"stop_loss_ratio":-0.024,"min_rate":0.015700000000000002,"max_rate":0.01596521,"is_open":false,"open_timestamp":1517090700000.0,"close_timestamp":1517136300000.0}],"locks":[],"best_pair":{"key":"ETC/BTC","trades":5,"profit_mean":0.012572794000000002,"profit_mean_pct":1.2572794000000003,"profit_sum":0.06286397,"profit_sum_pct":6.29,"profit_total_abs":0.0031542000000000002,"profit_total":3.1542000000000002e-06,"profit_total_pct":0.0,"duration_avg":"7:49:00","wins":2,"draws":2,"losses":1},"worst_pair":{"key":"LTC/BTC","trades":8,"profit_mean":-0.0077020425,"profit_mean_pct":-0.77020425,"profit_sum":-0.06161634,"profit_sum_pct":-6.16,"profit_total_abs":-0.0030916,"profit_total":-3.0915999999999998e-06,"profit_total_pct":-0.0,"duration_avg":"9:50:00","wins":0,"draws":6,"losses":2},"results_per_pair":[{"key":"ETC/BTC","trades":5,"profit_mean":0.012572794000000002,"profit_mean_pct":1.2572794000000003,"profit_sum":0.06286397,"profit_sum_pct":6.29,"profit_total_abs":0.0031542000000000002,"profit_total":3.1542000000000002e-06,"profit_total_pct":0.0,"duration_avg":"7:49:00","wins":2,"draws":2,"losses":1},{"key":"ETH/BTC","trades":3,"profit_mean":0.00498256,"profit_mean_pct":0.498256,"profit_sum":0.01494768,"profit_sum_pct":1.49,"profit_total_abs":0.00075,"profit_total":7.5e-07,"profit_total_pct":0.0,"duration_avg":"2:02:00","wins":1,"draws":2,"losses":0},{"key":"ADA/BTC","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0.0,"profit_sum_pct":0.0,"profit_total_abs":0.0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0},{"key":"XLM/BTC","trades":1,"profit_mean":-0.03080817,"profit_mean_pct":-3.080817,"profit_sum":-0.03080817,"profit_sum_pct":-3.08,"profit_total_abs":-0.0015458,"profit_total":-1.5457999999999999e-06,"profit_total_pct":-0.0,"duration_avg":"3:55:00","wins":0,"draws":0,"losses":1},{"key":"TRX/BTC","trades":5,"profit_mean":-0.009333732,"profit_mean_pct":-0.9333732000000001,"profit_sum":-0.04666866,"profit_sum_pct":-4.67,"profit_total_abs":-0.0023416,"profit_total":-2.3416e-06,"profit_total_pct":-0.0,"duration_avg":"1:45:00","wins":1,"draws":2,"losses":2},{"key":"LTC/BTC","trades":8,"profit_mean":-0.0077020425,"profit_mean_pct":-0.77020425,"profit_sum":-0.06161634,"profit_sum_pct":-6.16,"profit_total_abs":-0.0030916,"profit_total":-3.0915999999999998e-06,"profit_total_pct":-0.0,"duration_avg":"9:50:00","wins":0,"draws":6,"losses":2},{"key":"TOTAL","trades":22,"profit_mean":-0.0027855236363636365,"profit_mean_pct":-0.27855236363636365,"profit_sum":-0.06128152,"profit_sum_pct":-6.13,"profit_total_abs":-0.0030748,"profit_total":-3.0747999999999998e-06,"profit_total_pct":-0.0,"duration_avg":"6:12:00","wins":4,"draws":12,"losses":6}],"sell_reason_summary":[{"sell_reason":"roi","trades":16,"wins":4,"draws":12,"losses":0,"profit_mean":0.00772296875,"profit_mean_pct":0.77,"profit_sum":0.1235675,"profit_sum_pct":12.36,"profit_total_abs":0.006200000000000001,"profit_total":0.041189166666666666,"profit_total_pct":4.12},{"sell_reason":"stop_loss","trades":6,"wins":0,"draws":0,"losses":6,"profit_mean":-0.03080817,"profit_mean_pct":-3.08,"profit_sum":-0.18484902,"profit_sum_pct":-18.48,"profit_total_abs":-0.0092748,"profit_total":-0.06161634,"profit_total_pct":-6.16}],"left_open_trades":[{"key":"TOTAL","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0.0,"profit_sum_pct":0.0,"profit_total_abs":0.0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0}],"total_trades":22,"total_volume":1.1000000000000003,"avg_stake_amount":0.05000000000000002,"profit_mean":-0.0027855236363636365,"profit_median":0.0,"profit_total":-3.0747999999999998e-06,"profit_total_abs":-0.0030748,"backtest_start":"2018-01-10 07:25:00","backtest_start_ts":1515569100000,"backtest_end":"2018-01-30 04:45:00","backtest_end_ts":1517287500000,"backtest_days":19,"backtest_run_start_ts":1620793107,"backtest_run_end_ts":1620793108,"trades_per_day":1.16,"market_change":0,"pairlist":["ETH/BTC","LTC/BTC","ETC/BTC","XLM/BTC","TRX/BTC","ADA/BTC"],"stake_amount":0.05,"stake_currency":"BTC","stake_currency_decimals":8,"starting_balance":1000,"dry_run_wallet":1000,"final_balance":999.9969252,"max_open_trades":3,"max_open_trades_setting":3,"timeframe":"5m","timerange":"","enable_protections":false,"strategy_name":"SampleStrategy","stoploss":-0.1,"trailing_stop":false,"trailing_stop_positive":null,"trailing_stop_positive_offset":0.0,"trailing_only_offset_is_reached":false,"use_custom_stoploss":false,"minimal_roi":{"60":0.01,"30":0.02,"0":0.04},"use_sell_signal":true,"sell_profit_only":false,"sell_profit_offset":0.0,"ignore_roi_if_buy_signal":false,"backtest_best_day":0.07872446,"backtest_worst_day":-0.09242451,"backtest_best_day_abs":0.00395,"backtest_worst_day_abs":-0.0046374,"winning_days":4,"draw_days":10,"losing_days":4,"wins":4,"losses":6,"draws":12,"holding_avg":"6:12:00","winner_holding_avg":"1:29:00","loser_holding_avg":"4:42:00","max_drawdown":0.18484901999999998,"max_drawdown_abs":0.0092748,"drawdown_start":"2018-01-14 23:15:00","drawdown_start_ts":1515971700000.0,"drawdown_end":"2018-01-23 09:40:00","drawdown_end_ts":1516700400000.0,"max_drawdown_low":-0.0038247999999999997,"max_drawdown_high":0.00545,"csum_min":999.9961752,"csum_max":1000.00545},"results_explanation":" 22 trades. 4/12/6 Wins/Draws/Losses. Avg profit -0.28%. Median profit 0.00%. Total profit -0.00307480 BTC ( -0.00\u03A3%). Avg duration 6:12:00 min.","total_profit":-3.0747999999999998e-06,"current_epoch":3,"is_initial_point":true,"is_best":true}
+{"loss":-4.9544427978437175,"params_dict":{"mfi-value":"23","fastd-value":"40","adx-value":"50","rsi-value":"27","mfi-enabled":false,"fastd-enabled":true,"adx-enabled":true,"rsi-enabled":true,"trigger":"bb_lower","sell-mfi-value":"87","sell-fastd-value":"60","sell-adx-value":"81","sell-rsi-value":"69","sell-mfi-enabled":true,"sell-fastd-enabled":true,"sell-adx-enabled":false,"sell-rsi-enabled":false,"sell-trigger":"sell-sar_reversal","roi_t1":"105","roi_t2":"43","roi_t3":"12","roi_p1":0.03,"roi_p2":0.036,"roi_p3":0.103,"stoploss":-0.081},"params_details":{"buy":{"mfi-value":"23","fastd-value":"40","adx-value":"50","rsi-value":"27","mfi-enabled":false,"fastd-enabled":true,"adx-enabled":true,"rsi-enabled":true,"trigger":"bb_lower"},"sell":{"sell-mfi-value":"87","sell-fastd-value":"60","sell-adx-value":"81","sell-rsi-value":"69","sell-mfi-enabled":true,"sell-fastd-enabled":true,"sell-adx-enabled":false,"sell-rsi-enabled":false,"sell-trigger":"sell-sar_reversal"},"roi":"{0: 0.16899999999999998, 12: 0.066, 55: 0.03, 160: 0}","stoploss":{"stoploss":-0.081}},"params_not_optimized":{"buy":{},"sell":{}},"results_metrics":{"trades":[{"pair":"XLM/BTC","stake_amount":0.05,"amount":1086.95652174,"open_date":"2018-01-13 13:30:00+00:00","close_date":"2018-01-13 16:30:00+00:00","open_rate":4.6e-05,"close_rate":4.632313095835424e-05,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":180,"profit_ratio":0.0,"profit_abs":0.0,"sell_reason":"roi","initial_stop_loss_abs":4.2274e-05,"initial_stop_loss_ratio":-0.081,"stop_loss_abs":4.2274e-05,"stop_loss_ratio":-0.081,"min_rate":4.4980000000000006e-05,"max_rate":4.673e-05,"is_open":false,"open_timestamp":1515850200000.0,"close_timestamp":1515861000000.0},{"pair":"ADA/BTC","stake_amount":0.05,"amount":851.35365231,"open_date":"2018-01-15 14:50:00+00:00","close_date":"2018-01-15 16:15:00+00:00","open_rate":5.873000000000001e-05,"close_rate":6.0910642247867544e-05,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":85,"profit_ratio":0.02989537,"profit_abs":0.0015,"sell_reason":"roi","initial_stop_loss_abs":5.397287000000001e-05,"initial_stop_loss_ratio":-0.081,"stop_loss_abs":5.397287000000001e-05,"stop_loss_ratio":-0.081,"min_rate":5.873000000000001e-05,"max_rate":6.120000000000001e-05,"is_open":false,"open_timestamp":1516027800000.0,"close_timestamp":1516032900000.0},{"pair":"ADA/BTC","stake_amount":0.05,"amount":896.86098655,"open_date":"2018-01-16 00:35:00+00:00","close_date":"2018-01-16 03:15:00+00:00","open_rate":5.575000000000001e-05,"close_rate":5.6960000000000004e-05,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":160,"profit_ratio":0.01457705,"profit_abs":0.0007314,"sell_reason":"roi","initial_stop_loss_abs":5.123425000000001e-05,"initial_stop_loss_ratio":-0.081,"stop_loss_abs":5.123425000000001e-05,"stop_loss_ratio":-0.081,"min_rate":5.575000000000001e-05,"max_rate":5.730000000000001e-05,"is_open":false,"open_timestamp":1516062900000.0,"close_timestamp":1516072500000.0},{"pair":"TRX/BTC","stake_amount":0.05,"amount":747.160789,"open_date":"2018-01-16 22:30:00+00:00","close_date":"2018-01-16 22:45:00+00:00","open_rate":6.692e-05,"close_rate":7.182231811339689e-05,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":15,"profit_ratio":0.06576981,"profit_abs":0.0033,"sell_reason":"roi","initial_stop_loss_abs":6.149948000000001e-05,"initial_stop_loss_ratio":-0.081,"stop_loss_abs":6.149948000000001e-05,"stop_loss_ratio":-0.081,"min_rate":6.692e-05,"max_rate":7.566e-05,"is_open":false,"open_timestamp":1516141800000.0,"close_timestamp":1516142700000.0},{"pair":"TRX/BTC","stake_amount":0.05,"amount":720.5649229,"open_date":"2018-01-17 15:15:00+00:00","close_date":"2018-01-17 16:40:00+00:00","open_rate":6.939000000000001e-05,"close_rate":7.19664475664827e-05,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":85,"profit_ratio":0.02989537,"profit_abs":0.0015,"sell_reason":"roi","initial_stop_loss_abs":6.376941000000001e-05,"initial_stop_loss_ratio":-0.081,"stop_loss_abs":6.376941000000001e-05,"stop_loss_ratio":-0.081,"min_rate":6.758e-05,"max_rate":7.244e-05,"is_open":false,"open_timestamp":1516202100000.0,"close_timestamp":1516207200000.0},{"pair":"XLM/BTC","stake_amount":0.05,"amount":1144.42664225,"open_date":"2018-01-18 22:20:00+00:00","close_date":"2018-01-19 00:35:00+00:00","open_rate":4.3690000000000004e-05,"close_rate":4.531220772704466e-05,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":135,"profit_ratio":0.02989537,"profit_abs":0.0015,"sell_reason":"roi","initial_stop_loss_abs":4.015111e-05,"initial_stop_loss_ratio":-0.081,"stop_loss_abs":4.015111e-05,"stop_loss_ratio":-0.081,"min_rate":4.3690000000000004e-05,"max_rate":4.779e-05,"is_open":false,"open_timestamp":1516314000000.0,"close_timestamp":1516322100000.0},{"pair":"ADA/BTC","stake_amount":0.05,"amount":876.57784011,"open_date":"2018-01-18 22:25:00+00:00","close_date":"2018-01-19 01:05:00+00:00","open_rate":5.704e-05,"close_rate":5.792e-05,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":160,"profit_ratio":0.00834457,"profit_abs":0.00041869,"sell_reason":"roi","initial_stop_loss_abs":5.2419760000000006e-05,"initial_stop_loss_ratio":-0.081,"stop_loss_abs":5.2419760000000006e-05,"stop_loss_ratio":-0.081,"min_rate":5.704e-05,"max_rate":5.8670000000000006e-05,"is_open":false,"open_timestamp":1516314300000.0,"close_timestamp":1516323900000.0},{"pair":"TRX/BTC","stake_amount":0.05,"amount":525.59655209,"open_date":"2018-01-20 05:05:00+00:00","close_date":"2018-01-20 06:25:00+00:00","open_rate":9.513e-05,"close_rate":9.86621726041144e-05,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":80,"profit_ratio":0.02989537,"profit_abs":0.0015,"sell_reason":"roi","initial_stop_loss_abs":8.742447000000001e-05,"initial_stop_loss_ratio":-0.081,"stop_loss_abs":8.742447000000001e-05,"stop_loss_ratio":-0.081,"min_rate":9.513e-05,"max_rate":9.95e-05,"is_open":false,"open_timestamp":1516424700000.0,"close_timestamp":1516429500000.0},{"pair":"ADA/BTC","stake_amount":0.05,"amount":920.64076597,"open_date":"2018-01-26 07:40:00+00:00","close_date":"2018-01-26 10:20:00+00:00","open_rate":5.431000000000001e-05,"close_rate":5.474000000000001e-05,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":160,"profit_ratio":0.0008867,"profit_abs":4.449e-05,"sell_reason":"roi","initial_stop_loss_abs":4.991089000000001e-05,"initial_stop_loss_ratio":-0.081,"stop_loss_abs":4.991089000000001e-05,"stop_loss_ratio":-0.081,"min_rate":5.3670000000000006e-05,"max_rate":5.5e-05,"is_open":false,"open_timestamp":1516952400000.0,"close_timestamp":1516962000000.0},{"pair":"XLM/BTC","stake_amount":0.05,"amount":944.28706327,"open_date":"2018-01-28 04:35:00+00:00","close_date":"2018-01-30 04:45:00+00:00","open_rate":5.2950000000000006e-05,"close_rate":4.995000000000001e-05,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":2890,"profit_ratio":-0.06323759,"profit_abs":-0.00317295,"sell_reason":"force_sell","initial_stop_loss_abs":4.866105000000001e-05,"initial_stop_loss_ratio":-0.081,"stop_loss_abs":4.866105000000001e-05,"stop_loss_ratio":-0.081,"min_rate":4.980000000000001e-05,"max_rate":5.3280000000000005e-05,"is_open":true,"open_timestamp":1517114100000.0,"close_timestamp":1517287500000.0}],"locks":[],"best_pair":{"key":"TRX/BTC","trades":3,"profit_mean":0.04185351666666667,"profit_mean_pct":4.185351666666667,"profit_sum":0.12556055,"profit_sum_pct":12.56,"profit_total_abs":0.0063,"profit_total":6.3e-06,"profit_total_pct":0.0,"duration_avg":"1:00:00","wins":3,"draws":0,"losses":0},"worst_pair":{"key":"XLM/BTC","trades":3,"profit_mean":-0.01111407333333333,"profit_mean_pct":-1.111407333333333,"profit_sum":-0.03334221999999999,"profit_sum_pct":-3.33,"profit_total_abs":-0.0016729499999999999,"profit_total":-1.6729499999999998e-06,"profit_total_pct":-0.0,"duration_avg":"17:48:00","wins":1,"draws":1,"losses":1},"results_per_pair":[{"key":"TRX/BTC","trades":3,"profit_mean":0.04185351666666667,"profit_mean_pct":4.185351666666667,"profit_sum":0.12556055,"profit_sum_pct":12.56,"profit_total_abs":0.0063,"profit_total":6.3e-06,"profit_total_pct":0.0,"duration_avg":"1:00:00","wins":3,"draws":0,"losses":0},{"key":"ADA/BTC","trades":4,"profit_mean":0.0134259225,"profit_mean_pct":1.34259225,"profit_sum":0.05370369,"profit_sum_pct":5.37,"profit_total_abs":0.00269458,"profit_total":2.69458e-06,"profit_total_pct":0.0,"duration_avg":"2:21:00","wins":4,"draws":0,"losses":0},{"key":"ETH/BTC","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0.0,"profit_sum_pct":0.0,"profit_total_abs":0.0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0},{"key":"LTC/BTC","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0.0,"profit_sum_pct":0.0,"profit_total_abs":0.0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0},{"key":"ETC/BTC","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0.0,"profit_sum_pct":0.0,"profit_total_abs":0.0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0},{"key":"XLM/BTC","trades":3,"profit_mean":-0.01111407333333333,"profit_mean_pct":-1.111407333333333,"profit_sum":-0.03334221999999999,"profit_sum_pct":-3.33,"profit_total_abs":-0.0016729499999999999,"profit_total":-1.6729499999999998e-06,"profit_total_pct":-0.0,"duration_avg":"17:48:00","wins":1,"draws":1,"losses":1},{"key":"TOTAL","trades":10,"profit_mean":0.014592201999999999,"profit_mean_pct":1.4592201999999999,"profit_sum":0.14592201999999999,"profit_sum_pct":14.59,"profit_total_abs":0.00732163,"profit_total":7.32163e-06,"profit_total_pct":0.0,"duration_avg":"6:35:00","wins":8,"draws":1,"losses":1}],"sell_reason_summary":[{"sell_reason":"roi","trades":9,"wins":8,"draws":1,"losses":0,"profit_mean":0.023239956666666665,"profit_mean_pct":2.32,"profit_sum":0.20915961,"profit_sum_pct":20.92,"profit_total_abs":0.01049458,"profit_total":0.06971987,"profit_total_pct":6.97},{"sell_reason":"force_sell","trades":1,"wins":0,"draws":0,"losses":1,"profit_mean":-0.06323759,"profit_mean_pct":-6.32,"profit_sum":-0.06323759,"profit_sum_pct":-6.32,"profit_total_abs":-0.00317295,"profit_total":-0.021079196666666664,"profit_total_pct":-2.11}],"left_open_trades":[{"key":"XLM/BTC","trades":1,"profit_mean":-0.06323759,"profit_mean_pct":-6.323759,"profit_sum":-0.06323759,"profit_sum_pct":-6.32,"profit_total_abs":-0.00317295,"profit_total":-3.17295e-06,"profit_total_pct":-0.0,"duration_avg":"2 days, 0:10:00","wins":0,"draws":0,"losses":1},{"key":"TOTAL","trades":1,"profit_mean":-0.06323759,"profit_mean_pct":-6.323759,"profit_sum":-0.06323759,"profit_sum_pct":-6.32,"profit_total_abs":-0.00317295,"profit_total":-3.17295e-06,"profit_total_pct":-0.0,"duration_avg":"2 days, 0:10:00","wins":0,"draws":0,"losses":1}],"total_trades":10,"total_volume":0.5,"avg_stake_amount":0.05,"profit_mean":0.014592201999999999,"profit_median":0.02223621,"profit_total":7.32163e-06,"profit_total_abs":0.00732163,"backtest_start":"2018-01-10 07:25:00","backtest_start_ts":1515569100000,"backtest_end":"2018-01-30 04:45:00","backtest_end_ts":1517287500000,"backtest_days":19,"backtest_run_start_ts":1620793107,"backtest_run_end_ts":1620793108,"trades_per_day":0.53,"market_change":0,"pairlist":["ETH/BTC","LTC/BTC","ETC/BTC","XLM/BTC","TRX/BTC","ADA/BTC"],"stake_amount":0.05,"stake_currency":"BTC","stake_currency_decimals":8,"starting_balance":1000,"dry_run_wallet":1000,"final_balance":1000.00732163,"max_open_trades":3,"max_open_trades_setting":3,"timeframe":"5m","timerange":"","enable_protections":false,"strategy_name":"SampleStrategy","stoploss":-0.1,"trailing_stop":false,"trailing_stop_positive":null,"trailing_stop_positive_offset":0.0,"trailing_only_offset_is_reached":false,"use_custom_stoploss":false,"minimal_roi":{"60":0.01,"30":0.02,"0":0.04},"use_sell_signal":true,"sell_profit_only":false,"sell_profit_offset":0.0,"ignore_roi_if_buy_signal":false,"backtest_best_day":0.08034685999999999,"backtest_worst_day":-0.06323759,"backtest_best_day_abs":0.0040314,"backtest_worst_day_abs":-0.00317295,"winning_days":6,"draw_days":11,"losing_days":1,"wins":8,"losses":1,"draws":1,"holding_avg":"6:35:00","winner_holding_avg":"1:50:00","loser_holding_avg":"2 days, 0:10:00","max_drawdown":0.06323759000000001,"max_drawdown_abs":0.00317295,"drawdown_start":"2018-01-26 10:20:00","drawdown_start_ts":1516962000000.0,"drawdown_end":"2018-01-30 04:45:00","drawdown_end_ts":1517287500000.0,"max_drawdown_low":0.007321629999999998,"max_drawdown_high":0.010494579999999998,"csum_min":1000.0,"csum_max":1000.01049458},"results_explanation":" 10 trades. 8/1/1 Wins/Draws/Losses. Avg profit 1.46%. Median profit 2.22%. Total profit 0.00732163 BTC ( 0.00\u03A3%). Avg duration 6:35:00 min.","total_profit":7.32163e-06,"current_epoch":4,"is_initial_point":true,"is_best":true}
+{"loss":0.16709185414267655,"params_dict":{"mfi-value":"10","fastd-value":"45","adx-value":"28","rsi-value":"37","mfi-enabled":false,"fastd-enabled":false,"adx-enabled":true,"rsi-enabled":true,"trigger":"macd_cross_signal","sell-mfi-value":"85","sell-fastd-value":"56","sell-adx-value":"98","sell-rsi-value":"89","sell-mfi-enabled":true,"sell-fastd-enabled":false,"sell-adx-enabled":true,"sell-rsi-enabled":false,"sell-trigger":"sell-sar_reversal","roi_t1":"85","roi_t2":"11","roi_t3":"24","roi_p1":0.04,"roi_p2":0.043,"roi_p3":0.053,"stoploss":-0.057},"params_details":{"buy":{"mfi-value":"10","fastd-value":"45","adx-value":"28","rsi-value":"37","mfi-enabled":false,"fastd-enabled":false,"adx-enabled":true,"rsi-enabled":true,"trigger":"macd_cross_signal"},"sell":{"sell-mfi-value":"85","sell-fastd-value":"56","sell-adx-value":"98","sell-rsi-value":"89","sell-mfi-enabled":true,"sell-fastd-enabled":false,"sell-adx-enabled":true,"sell-rsi-enabled":false,"sell-trigger":"sell-sar_reversal"},"roi":"{0: 0.13599999999999998, 24: 0.08299999999999999, 35: 0.04, 120: 0}","stoploss":{"stoploss":-0.057}},"params_not_optimized":{"buy":{},"sell":{}},"results_metrics":{"trades":[{"pair":"ETH/BTC","stake_amount":0.05,"amount":0.56173464,"open_date":"2018-01-10 19:15:00+00:00","close_date":"2018-01-10 21:15:00+00:00","open_rate":0.08901,"close_rate":0.09112999000000001,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":120,"profit_ratio":0.01667571,"profit_abs":0.0008367,"sell_reason":"roi","initial_stop_loss_abs":0.08393643,"initial_stop_loss_ratio":-0.057,"stop_loss_abs":0.08393643,"stop_loss_ratio":-0.057,"min_rate":0.08894498,"max_rate":0.09116998,"is_open":false,"open_timestamp":1515611700000.0,"close_timestamp":1515618900000.0},{"pair":"ADA/BTC","stake_amount":0.05,"amount":794.65988557,"open_date":"2018-01-13 11:30:00+00:00","close_date":"2018-01-13 15:10:00+00:00","open_rate":6.292e-05,"close_rate":5.9333559999999994e-05,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":220,"profit_ratio":-0.06357798,"profit_abs":-0.00319003,"sell_reason":"stop_loss","initial_stop_loss_abs":5.9333559999999994e-05,"initial_stop_loss_ratio":-0.057,"stop_loss_abs":5.9333559999999994e-05,"stop_loss_ratio":-0.057,"min_rate":5.9900000000000006e-05,"max_rate":6.353e-05,"is_open":false,"open_timestamp":1515843000000.0,"close_timestamp":1515856200000.0},{"pair":"XLM/BTC","stake_amount":0.05,"amount":1086.95652174,"open_date":"2018-01-13 14:35:00+00:00","close_date":"2018-01-13 21:40:00+00:00","open_rate":4.6e-05,"close_rate":4.632313095835424e-05,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":425,"profit_ratio":0.0,"profit_abs":0.0,"sell_reason":"roi","initial_stop_loss_abs":4.3378e-05,"initial_stop_loss_ratio":-0.057,"stop_loss_abs":4.3378e-05,"stop_loss_ratio":-0.057,"min_rate":4.4980000000000006e-05,"max_rate":4.6540000000000005e-05,"is_open":false,"open_timestamp":1515854100000.0,"close_timestamp":1515879600000.0},{"pair":"ETH/BTC","stake_amount":0.05,"amount":0.53757603,"open_date":"2018-01-15 13:15:00+00:00","close_date":"2018-01-15 15:15:00+00:00","open_rate":0.0930101,"close_rate":0.09366345745107878,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":120,"profit_ratio":0.0,"profit_abs":0.0,"sell_reason":"roi","initial_stop_loss_abs":0.0877085243,"initial_stop_loss_ratio":-0.057,"stop_loss_abs":0.0877085243,"stop_loss_ratio":-0.057,"min_rate":0.09188489999999999,"max_rate":0.09380000000000001,"is_open":false,"open_timestamp":1516022100000.0,"close_timestamp":1516029300000.0},{"pair":"ETC/BTC","stake_amount":0.05,"amount":17.07469496,"open_date":"2018-01-15 14:35:00+00:00","close_date":"2018-01-15 16:35:00+00:00","open_rate":0.00292831,"close_rate":0.00297503,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":120,"profit_ratio":0.00886772,"profit_abs":0.00044494,"sell_reason":"roi","initial_stop_loss_abs":0.0027613963299999997,"initial_stop_loss_ratio":-0.057,"stop_loss_abs":0.0027613963299999997,"stop_loss_ratio":-0.057,"min_rate":0.00292831,"max_rate":0.00301259,"is_open":false,"open_timestamp":1516026900000.0,"close_timestamp":1516034100000.0},{"pair":"TRX/BTC","stake_amount":0.05,"amount":702.44450688,"open_date":"2018-01-17 04:25:00+00:00","close_date":"2018-01-17 05:00:00+00:00","open_rate":7.118e-05,"close_rate":7.453721023582538e-05,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":35,"profit_ratio":0.03986049,"profit_abs":0.002,"sell_reason":"roi","initial_stop_loss_abs":6.712274e-05,"initial_stop_loss_ratio":-0.057,"stop_loss_abs":6.712274e-05,"stop_loss_ratio":-0.057,"min_rate":7.118e-05,"max_rate":7.658000000000002e-05,"is_open":false,"open_timestamp":1516163100000.0,"close_timestamp":1516165200000.0},{"pair":"ETC/BTC","stake_amount":0.05,"amount":18.86756854,"open_date":"2018-01-20 06:05:00+00:00","close_date":"2018-01-20 08:05:00+00:00","open_rate":0.00265005,"close_rate":0.00266995,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":120,"profit_ratio":0.00048133,"profit_abs":2.415e-05,"sell_reason":"roi","initial_stop_loss_abs":0.00249899715,"initial_stop_loss_ratio":-0.057,"stop_loss_abs":0.00249899715,"stop_loss_ratio":-0.057,"min_rate":0.00265005,"max_rate":0.00271,"is_open":false,"open_timestamp":1516428300000.0,"close_timestamp":1516435500000.0},{"pair":"ADA/BTC","stake_amount":0.05,"amount":966.18357488,"open_date":"2018-01-22 03:25:00+00:00","close_date":"2018-01-22 07:05:00+00:00","open_rate":5.1750000000000004e-05,"close_rate":5.211352232814853e-05,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":220,"profit_ratio":0.0,"profit_abs":0.0,"sell_reason":"roi","initial_stop_loss_abs":4.8800250000000004e-05,"initial_stop_loss_ratio":-0.057,"stop_loss_abs":4.8800250000000004e-05,"stop_loss_ratio":-0.057,"min_rate":5.1750000000000004e-05,"max_rate":5.2170000000000004e-05,"is_open":false,"open_timestamp":1516591500000.0,"close_timestamp":1516604700000.0},{"pair":"ETC/BTC","stake_amount":0.05,"amount":18.95303438,"open_date":"2018-01-23 13:10:00+00:00","close_date":"2018-01-23 16:00:00+00:00","open_rate":0.0026381,"close_rate":0.002656631560461616,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":170,"profit_ratio":0.0,"profit_abs":0.0,"sell_reason":"roi","initial_stop_loss_abs":0.0024877283,"initial_stop_loss_ratio":-0.057,"stop_loss_abs":0.0024877283,"stop_loss_ratio":-0.057,"min_rate":0.0026100000000000003,"max_rate":0.00266,"is_open":false,"open_timestamp":1516713000000.0,"close_timestamp":1516723200000.0},{"pair":"ADA/BTC","stake_amount":0.05,"amount":912.40875912,"open_date":"2018-01-26 06:30:00+00:00","close_date":"2018-01-26 10:45:00+00:00","open_rate":5.480000000000001e-05,"close_rate":5.518494731560462e-05,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":255,"profit_ratio":-0.0,"profit_abs":-0.0,"sell_reason":"roi","initial_stop_loss_abs":5.1676400000000006e-05,"initial_stop_loss_ratio":-0.057,"stop_loss_abs":5.1676400000000006e-05,"stop_loss_ratio":-0.057,"min_rate":5.3670000000000006e-05,"max_rate":5.523e-05,"is_open":false,"open_timestamp":1516948200000.0,"close_timestamp":1516963500000.0},{"pair":"ADA/BTC","stake_amount":0.05,"amount":909.58704748,"open_date":"2018-01-27 02:10:00+00:00","close_date":"2018-01-27 05:40:00+00:00","open_rate":5.4970000000000004e-05,"close_rate":5.535614149523332e-05,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":210,"profit_ratio":0.0,"profit_abs":0.0,"sell_reason":"roi","initial_stop_loss_abs":5.183671e-05,"initial_stop_loss_ratio":-0.057,"stop_loss_abs":5.183671e-05,"stop_loss_ratio":-0.057,"min_rate":5.472000000000001e-05,"max_rate":5.556e-05,"is_open":false,"open_timestamp":1517019000000.0,"close_timestamp":1517031600000.0}],"locks":[],"best_pair":{"key":"TRX/BTC","trades":1,"profit_mean":0.03986049,"profit_mean_pct":3.986049,"profit_sum":0.03986049,"profit_sum_pct":3.99,"profit_total_abs":0.002,"profit_total":2e-06,"profit_total_pct":0.0,"duration_avg":"0:35:00","wins":1,"draws":0,"losses":0},"worst_pair":{"key":"ADA/BTC","trades":4,"profit_mean":-0.015894495,"profit_mean_pct":-1.5894495000000002,"profit_sum":-0.06357798,"profit_sum_pct":-6.36,"profit_total_abs":-0.00319003,"profit_total":-3.19003e-06,"profit_total_pct":-0.0,"duration_avg":"3:46:00","wins":0,"draws":3,"losses":1},"results_per_pair":[{"key":"TRX/BTC","trades":1,"profit_mean":0.03986049,"profit_mean_pct":3.986049,"profit_sum":0.03986049,"profit_sum_pct":3.99,"profit_total_abs":0.002,"profit_total":2e-06,"profit_total_pct":0.0,"duration_avg":"0:35:00","wins":1,"draws":0,"losses":0},{"key":"ETH/BTC","trades":2,"profit_mean":0.008337855,"profit_mean_pct":0.8337855,"profit_sum":0.01667571,"profit_sum_pct":1.67,"profit_total_abs":0.0008367,"profit_total":8.367e-07,"profit_total_pct":0.0,"duration_avg":"2:00:00","wins":1,"draws":1,"losses":0},{"key":"ETC/BTC","trades":3,"profit_mean":0.0031163500000000004,"profit_mean_pct":0.31163500000000005,"profit_sum":0.009349050000000001,"profit_sum_pct":0.93,"profit_total_abs":0.00046909,"profit_total":4.6909000000000003e-07,"profit_total_pct":0.0,"duration_avg":"2:17:00","wins":2,"draws":1,"losses":0},{"key":"LTC/BTC","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0.0,"profit_sum_pct":0.0,"profit_total_abs":0.0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0},{"key":"XLM/BTC","trades":1,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0.0,"profit_sum_pct":0.0,"profit_total_abs":0.0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"7:05:00","wins":0,"draws":1,"losses":0},{"key":"ADA/BTC","trades":4,"profit_mean":-0.015894495,"profit_mean_pct":-1.5894495000000002,"profit_sum":-0.06357798,"profit_sum_pct":-6.36,"profit_total_abs":-0.00319003,"profit_total":-3.19003e-06,"profit_total_pct":-0.0,"duration_avg":"3:46:00","wins":0,"draws":3,"losses":1},{"key":"TOTAL","trades":11,"profit_mean":0.00020975181818181756,"profit_mean_pct":0.020975181818181757,"profit_sum":0.002307269999999993,"profit_sum_pct":0.23,"profit_total_abs":0.00011576000000000034,"profit_total":1.1576000000000034e-07,"profit_total_pct":0.0,"duration_avg":"3:03:00","wins":4,"draws":6,"losses":1}],"sell_reason_summary":[{"sell_reason":"roi","trades":10,"wins":4,"draws":6,"losses":0,"profit_mean":0.0065885250000000005,"profit_mean_pct":0.66,"profit_sum":0.06588525,"profit_sum_pct":6.59,"profit_total_abs":0.0033057900000000003,"profit_total":0.021961750000000002,"profit_total_pct":2.2},{"sell_reason":"stop_loss","trades":1,"wins":0,"draws":0,"losses":1,"profit_mean":-0.06357798,"profit_mean_pct":-6.36,"profit_sum":-0.06357798,"profit_sum_pct":-6.36,"profit_total_abs":-0.00319003,"profit_total":-0.021192660000000002,"profit_total_pct":-2.12}],"left_open_trades":[{"key":"TOTAL","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0.0,"profit_sum_pct":0.0,"profit_total_abs":0.0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0}],"total_trades":11,"total_volume":0.55,"avg_stake_amount":0.05,"profit_mean":0.00020975181818181756,"profit_median":0.0,"profit_total":1.1576000000000034e-07,"profit_total_abs":0.00011576000000000034,"backtest_start":"2018-01-10 07:25:00","backtest_start_ts":1515569100000,"backtest_end":"2018-01-30 04:45:00","backtest_end_ts":1517287500000,"backtest_days":19,"backtest_run_start_ts":1620793107,"backtest_run_end_ts":1620793108,"trades_per_day":0.58,"market_change":0,"pairlist":["ETH/BTC","LTC/BTC","ETC/BTC","XLM/BTC","TRX/BTC","ADA/BTC"],"stake_amount":0.05,"stake_currency":"BTC","stake_currency_decimals":8,"starting_balance":1000,"dry_run_wallet":1000,"final_balance":1000.00011576,"max_open_trades":3,"max_open_trades_setting":3,"timeframe":"5m","timerange":"","enable_protections":false,"strategy_name":"SampleStrategy","stoploss":-0.1,"trailing_stop":false,"trailing_stop_positive":null,"trailing_stop_positive_offset":0.0,"trailing_only_offset_is_reached":false,"use_custom_stoploss":false,"minimal_roi":{"60":0.01,"30":0.02,"0":0.04},"use_sell_signal":true,"sell_profit_only":false,"sell_profit_offset":0.0,"ignore_roi_if_buy_signal":false,"backtest_best_day":0.03986049,"backtest_worst_day":-0.06357798,"backtest_best_day_abs":0.002,"backtest_worst_day_abs":-0.00319003,"winning_days":4,"draw_days":13,"losing_days":1,"wins":4,"losses":1,"draws":6,"holding_avg":"3:03:00","winner_holding_avg":"1:39:00","loser_holding_avg":"3:40:00","max_drawdown":0.06357798,"max_drawdown_abs":0.00319003,"drawdown_start":"2018-01-10 21:15:00","drawdown_start_ts":1515618900000.0,"drawdown_end":"2018-01-13 15:10:00","drawdown_end_ts":1515856200000.0,"max_drawdown_low":-0.00235333,"max_drawdown_high":0.0008367,"csum_min":999.99764667,"csum_max":1000.0008367},"results_explanation":" 11 trades. 4/6/1 Wins/Draws/Losses. Avg profit 0.02%. Median profit 0.00%. Total profit 0.00011576 BTC ( 0.00\u03A3%). Avg duration 3:03:00 min.","total_profit":1.1576000000000034e-07,"current_epoch":5,"is_initial_point":true,"is_best":false}