diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 8cf081c49..7ef537ba1 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -31,6 +31,10 @@ TOTAL_TRIES = None _CURRENT_TRIES = 0 CURRENT_BEST_LOSS = 100 +# max average trade duration in minutes +# if eval ends with higher value, we consider it a failed eval +MAX_ACCEPTED_TRADE_DURATION = 240 + # this is expexted avg profit * expected trade count # for example 3.5%, 1100 trades, EXPECTED_MAX_PROFIT = 3.85 EXPECTED_MAX_PROFIT = 3.85 @@ -91,6 +95,7 @@ SPACE = { {'type': 'stochf_cross'}, {'type': 'ht_sine'}, ]), + 'stoploss': hp.quniform('stoploss', -30, -2, 1), } @@ -109,11 +114,12 @@ def log_results(results): sys.stdout.flush() -def calculate_loss(total_profit: float, trade_count: int): +def calculate_loss(total_profit: float, trade_count: int, trade_duration: float): """ objective function, returns smaller number for more optimal results """ trade_loss = 1 - 0.35 * exp(-(trade_count - TARGET_TRADES) ** 2 / 10 ** 5.2) profit_loss = max(0, 1 - total_profit / EXPECTED_MAX_PROFIT) - return trade_loss + profit_loss + duration_loss = min(trade_duration / MAX_ACCEPTED_TRADE_DURATION, 1) + return trade_loss + profit_loss + duration_loss def optimizer(params): @@ -122,20 +128,21 @@ def optimizer(params): from freqtrade.optimize import backtesting backtesting.populate_buy_trend = buy_strategy_generator(params) - results = backtest(OPTIMIZE_CONFIG['stake_amount'], PROCESSED) + results = backtest(OPTIMIZE_CONFIG['stake_amount'], PROCESSED, stoploss=params['stoploss']) result_explanation = format_results(results) total_profit = results.profit_percent.sum() trade_count = len(results.index) + trade_duration = results.duration.mean() * 5 - if trade_count == 0: + if trade_count == 0 or trade_duration > MAX_ACCEPTED_TRADE_DURATION: print('.', end='') return { 'status': STATUS_FAIL, 'loss': float('inf') } - loss = calculate_loss(total_profit, trade_count) + loss = calculate_loss(total_profit, trade_count, trade_duration) _CURRENT_TRIES += 1 diff --git a/freqtrade/tests/optimize/test_hyperopt.py b/freqtrade/tests/optimize/test_hyperopt.py index b10dd673d..02cf31ec3 100644 --- a/freqtrade/tests/optimize/test_hyperopt.py +++ b/freqtrade/tests/optimize/test_hyperopt.py @@ -5,17 +5,23 @@ from freqtrade.optimize.hyperopt import calculate_loss, TARGET_TRADES, EXPECTED_ def test_loss_calculation_prefer_correct_trade_count(): - correct = calculate_loss(1, TARGET_TRADES) - over = calculate_loss(1, TARGET_TRADES + 100) - under = calculate_loss(1, TARGET_TRADES - 100) + correct = calculate_loss(1, TARGET_TRADES, 20) + over = calculate_loss(1, TARGET_TRADES + 100, 20) + under = calculate_loss(1, TARGET_TRADES - 100, 20) assert over > correct assert under > correct +def test_loss_calculation_prefer_shorter_trades(): + shorter = calculate_loss(1, 100, 20) + longer = calculate_loss(1, 100, 30) + assert shorter < longer + + def test_loss_calculation_has_limited_profit(): - correct = calculate_loss(EXPECTED_MAX_PROFIT, TARGET_TRADES) - over = calculate_loss(EXPECTED_MAX_PROFIT * 2, TARGET_TRADES) - under = calculate_loss(EXPECTED_MAX_PROFIT / 2, TARGET_TRADES) + correct = calculate_loss(EXPECTED_MAX_PROFIT, TARGET_TRADES, 20) + over = calculate_loss(EXPECTED_MAX_PROFIT * 2, TARGET_TRADES, 20) + under = calculate_loss(EXPECTED_MAX_PROFIT / 2, TARGET_TRADES, 20) assert over == correct assert under > correct @@ -93,7 +99,8 @@ def test_fmin_best_results(mocker, caplog): "trigger": 2, "uptrend_long_ema": 1, "uptrend_short_ema": 0, - "uptrend_sma": 0 + "uptrend_sma": 0, + "stoploss": -10, } mocker.patch('freqtrade.optimize.hyperopt.MongoTrials', return_value=create_trials(mocker)) @@ -109,7 +116,8 @@ def test_fmin_best_results(mocker, caplog): '"adx": {\n "enabled": true,\n "value": 15.0\n },', '"green_candle": {\n "enabled": true\n },', '"mfi": {\n "enabled": false\n },', - '"trigger": {\n "type": "ao_cross_zero"\n },' + '"trigger": {\n "type": "ao_cross_zero"\n },', + '"stoploss": -10.0', ] for line in exists: