Add sharpe ratio as loss function

This commit is contained in:
Matthias 2019-07-15 22:52:33 +02:00
parent e5170582de
commit 55e8092cbf
5 changed files with 73 additions and 13 deletions

View File

@ -151,6 +151,20 @@ The above setup expects to find ADX, RSI and Bollinger Bands in the populated in
When you want to test an indicator that isn't used by the bot currently, remember to
add it to the `populate_indicators()` method in `hyperopt.py`.
## Loss-functions
Each hyperparameter tuning requires a target. This is usually defined as a function, which get's closer to 0 for increasing values.
By default, freqtrade uses a loss function we call `legacy` - since it's been with freqtrade since the beginning and optimizes for short trade duration.
This can be configured by using the `--loss <value>` argument.
Possible options are:
* `legacy` - The default option, optimizing for short trades and few losses.
* `sharpe` - using the sharpe-ratio to determine the quality of results
* `custom` - Custom defined loss-function [see next section](#using-a-custom-loss-function)
### Using a custom loss function
To use a custom loss function, make sure that the function `hyperopt_loss_custom` is defined in your custom hyperopt class.

View File

@ -235,7 +235,7 @@ AVAILABLE_CLI_OPTIONS = {
help='Define the loss-function to use for hyperopt.'
'Possibilities are `legacy`, and `custom` (providing a custom loss-function).'
'Default: `%(default)s`.',
choices=['legacy', 'custom'],
choices=['legacy', 'sharpe', 'custom'],
default='legacy',
),
# List exchanges

View File

@ -23,7 +23,7 @@ from freqtrade.configuration import Arguments
from freqtrade.data.history import load_data, get_timeframe
from freqtrade.optimize.backtesting import Backtesting
from freqtrade.resolvers.hyperopt_resolver import HyperOptResolver
from freqtrade.optimize.hyperopt_loss import hyperopt_loss_legacy
from freqtrade.optimize.hyperopt_loss import hyperopt_loss_legacy, hyperopt_loss_sharpe
logger = logging.getLogger(__name__)
@ -74,6 +74,8 @@ class Hyperopt(Backtesting):
# Assign loss function
if self.config.get('loss_function', 'legacy') == 'legacy':
self.calculate_loss = hyperopt_loss_legacy
elif self.config.get('loss_function', 'sharpe') == 'sharpe':
self.calculate_loss = hyperopt_loss_sharpe
elif (self.config['loss_function'] == 'custom' and
hasattr(self.custom_hyperopt, 'hyperopt_loss_custom')):
self.calculate_loss = self.custom_hyperopt.hyperopt_loss_custom # type: ignore

View File

@ -1,4 +1,7 @@
from datetime import datetime
from math import exp
import numpy as np
from pandas import DataFrame
# Define some constants:
@ -35,3 +38,27 @@ def hyperopt_loss_legacy(results: DataFrame, trade_count: int,
duration_loss = 0.4 * min(trade_duration / MAX_ACCEPTED_TRADE_DURATION, 1)
result = trade_loss + profit_loss + duration_loss
return result
def hyperopt_loss_sharpe(results: DataFrame, trade_count: int,
min_date: datetime, max_date: datetime, *args, **kwargs) -> float:
"""
Objective function, returns smaller number for more optimal results
Using sharpe ratio calculation
"""
total_profit = results.profit_percent
days_period = (max_date - min_date).days
# adding slippage of 0.1% per trade
total_profit = total_profit - 0.0005
expected_yearly_return = total_profit.sum() / days_period
if (np.std(total_profit) != 0.):
sharp_ratio = expected_yearly_return / np.std(total_profit) * np.sqrt(365)
else:
sharp_ratio = 1.
# print(expected_yearly_return, np.std(total_profit), sharp_ratio)
# Negate sharp-ratio so lower is better (??)
return -sharp_ratio

View File

@ -15,6 +15,7 @@ from freqtrade.optimize import setup_configuration, start_hyperopt
from freqtrade.optimize.default_hyperopt import DefaultHyperOpts
from freqtrade.optimize.hyperopt import (HYPEROPT_LOCKFILE, TICKERDATA_PICKLE,
Hyperopt)
from freqtrade.optimize.hyperopt_loss import hyperopt_loss_legacy, hyperopt_loss_sharpe
from freqtrade.resolvers.hyperopt_resolver import HyperOptResolver
from freqtrade.state import RunMode
from freqtrade.strategy.interface import SellType
@ -273,32 +274,48 @@ def test_start_filelock(mocker, default_conf, caplog) -> None:
)
def test_loss_calculation_prefer_correct_trade_count(hyperopt, hyperopt_results) -> None:
correct = hyperopt.calculate_loss(hyperopt_results, hyperopt.target_trades)
over = hyperopt.calculate_loss(hyperopt_results, hyperopt.target_trades + 100)
under = hyperopt.calculate_loss(hyperopt_results, hyperopt.target_trades - 100)
def test_loss_calculation_prefer_correct_trade_count(hyperopt_results) -> None:
correct = hyperopt_loss_legacy(hyperopt_results, 600)
over = hyperopt_loss_legacy(hyperopt_results, 600 + 100)
under = hyperopt_loss_legacy(hyperopt_results, 600 - 100)
assert over > correct
assert under > correct
def test_loss_calculation_prefer_shorter_trades(hyperopt, hyperopt_results) -> None:
def test_loss_calculation_prefer_shorter_trades(hyperopt_results) -> None:
resultsb = hyperopt_results.copy()
resultsb['trade_duration'][1] = 20
longer = hyperopt.calculate_loss(hyperopt_results, 100)
shorter = hyperopt.calculate_loss(resultsb, 100)
longer = hyperopt_loss_legacy(hyperopt_results, 100)
shorter = hyperopt_loss_legacy(resultsb, 100)
assert shorter < longer
def test_loss_calculation_has_limited_profit(hyperopt, hyperopt_results) -> None:
def test_loss_calculation_has_limited_profit(hyperopt_results) -> None:
results_over = hyperopt_results.copy()
results_over['profit_percent'] = hyperopt_results['profit_percent'] * 2
results_under = hyperopt_results.copy()
results_under['profit_percent'] = hyperopt_results['profit_percent'] / 2
correct = hyperopt.calculate_loss(hyperopt_results, hyperopt.target_trades)
over = hyperopt.calculate_loss(results_over, hyperopt.target_trades)
under = hyperopt.calculate_loss(results_under, hyperopt.target_trades)
correct = hyperopt_loss_legacy(hyperopt_results, 600)
over = hyperopt_loss_legacy(results_over, 600)
under = hyperopt_loss_legacy(results_under, 600)
assert over < correct
assert under > correct
def test_sharpe_loss_prefers_higher_profits(hyperopt_results) -> None:
results_over = hyperopt_results.copy()
results_over['profit_percent'] = hyperopt_results['profit_percent'] * 2
results_under = hyperopt_results.copy()
results_under['profit_percent'] = hyperopt_results['profit_percent'] / 2
correct = hyperopt_loss_sharpe(hyperopt_results, len(
hyperopt_results), datetime(2019, 1, 1), datetime(2019, 5, 1))
over = hyperopt_loss_sharpe(results_over, len(hyperopt_results),
datetime(2019, 1, 1), datetime(2019, 5, 1))
under = hyperopt_loss_sharpe(results_under, len(hyperopt_results),
datetime(2019, 1, 1), datetime(2019, 5, 1))
assert over < correct
assert under > correct