hyperopt loss function based on calmar ratio
This commit is contained in:
parent
f987e6e0f9
commit
c5ee01fe4b
76
freqtrade/optimize/hyperopt_loss_calmar.py
Normal file
76
freqtrade/optimize/hyperopt_loss_calmar.py
Normal file
@ -0,0 +1,76 @@
|
||||
from datetime import datetime
|
||||
from pandas import DataFrame, Series
|
||||
from freqtrade.optimize.hyperopt import IHyperOptLoss
|
||||
import numpy as np
|
||||
from scipy.stats import norm
|
||||
|
||||
NB_SIMULATIONS = 1000
|
||||
SIMULATION_YEAR_DURATION = 3
|
||||
HIGH_NUMBER = 100000
|
||||
CALMAR_LOSS_WEIGHT = 1
|
||||
SLIPPAGE_PERCENT = 0.001
|
||||
|
||||
|
||||
class CalmarHyperOptLoss(IHyperOptLoss):
|
||||
"""
|
||||
Defines the default loss function for hyperopt
|
||||
This is intended to give you some inspiration for your own loss function.
|
||||
The Function needs to return a number (float) - which becomes for better backtest results.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def hyperopt_loss_function(cls, results: DataFrame, trade_count: int,
|
||||
min_date: datetime, max_date: datetime,
|
||||
*args, **kwargs) -> float:
|
||||
|
||||
"""
|
||||
Objective function, returns smaller number for better results
|
||||
"""
|
||||
|
||||
simulated_drawdowns = []
|
||||
|
||||
backtest_duration_years = ((max_date-min_date).days/365.2425)
|
||||
trade_count_average_per_year = trade_count/backtest_duration_years
|
||||
|
||||
# add slipage to be closed to live
|
||||
results['profit_percent'] -= SLIPPAGE_PERCENT
|
||||
|
||||
return_avg_per_year = (results.profit_percent.sum() / backtest_duration_years)
|
||||
return_avg_simulation_duration = return_avg_per_year * SIMULATION_YEAR_DURATION
|
||||
|
||||
sample_size = round(trade_count_average_per_year * SIMULATION_YEAR_DURATION)
|
||||
|
||||
# exclude the case when no trade was lost
|
||||
if(results.profit_percent.min() >= 0):
|
||||
return HIGH_NUMBER
|
||||
|
||||
# simulate n years of run to define a median max drawdown
|
||||
for i in range(0, NB_SIMULATIONS):
|
||||
randomized_result = results.profit_percent.sample(n=sample_size,
|
||||
random_state=np.random.RandomState(),
|
||||
replace=True)
|
||||
simulated_drawdown = cls.abs_max_drawdown(randomized_result)
|
||||
simulated_drawdowns.append(simulated_drawdown)
|
||||
|
||||
abs_mediam_simulated_drawdowns = Series(simulated_drawdowns).median()
|
||||
calmar_ratio = return_avg_simulation_duration/abs_mediam_simulated_drawdowns
|
||||
|
||||
# float between ]0,1[
|
||||
calmar_loss = 1 - (norm.cdf(calmar_ratio, 0, 100))
|
||||
|
||||
# feel free to add other criterias (e.g avg expected time duration)
|
||||
loss = (calmar_loss * CALMAR_LOSS_WEIGHT)
|
||||
|
||||
# print('calmar_ratio {}'.format(calmar_ratio))
|
||||
|
||||
return loss
|
||||
|
||||
@staticmethod
|
||||
def abs_max_drawdown(profit_percent_results: Series) -> float:
|
||||
|
||||
max_drawdown_df = DataFrame()
|
||||
max_drawdown_df['cumulative'] = profit_percent_results.cumsum()
|
||||
max_drawdown_df['high_value'] = max_drawdown_df['cumulative'].cummax()
|
||||
max_drawdown_df['drawdown'] = max_drawdown_df['cumulative'] - max_drawdown_df['high_value']
|
||||
|
||||
return abs(max_drawdown_df['drawdown'].min())
|
Loading…
Reference in New Issue
Block a user