stable/freqtrade/optimize/hyperopt_loss_calmar.py
2019-09-12 15:35:27 +02:00

81 lines
3.1 KiB
Python

from datetime import datetime
from pandas import DataFrame, Series
import numpy as np
from scipy.stats import norm
from freqtrade.optimize.hyperopt import IHyperOptLoss, MAX_LOSS
NB_SIMULATIONS = 1000
SIMULATION_YEAR_DURATION = 3
CALMAR_LOSS_WEIGHT = 1
SLIPPAGE_PERCENT = 0.001
class CalmarHyperOptLoss(IHyperOptLoss):
"""
Defines the calmar loss function for hyperopt.
Calmar ratio is based on average annual rate of return for the last 36 months divided by the
maximum drawdown for the last 36 months.
But you maybe don't have running hyperopt with 36 months of data so we will simulate 36 months
of trading with a montecarlo simulation and find the median drawdown (what's happenned if the trades
orders changes, the max drawdown change ?)
shorturl.at/ioAK2
"""
@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 MAX_LOSS
# 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
# Normalize loss value to be 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())