mirror of
https://github.com/freqtrade/freqtrade.git
synced 2024-11-10 10:21:59 +00:00
Add sharpe ratio as loss function
This commit is contained in:
parent
e5170582de
commit
55e8092cbf
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user