stable/freqtrade/optimize/hyperopt_loss_calmar.py
2019-09-16 12:01:24 +02:00

96 lines
3.7 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
SLIPPAGE_PERCENT = 0.000
NB_EXPECTED_TRADES = 600
CALMAR_LOSS_WEIGHT = 0.5
EXPECTED_TRADES_WEIGHT = 0.5
class CalmarHyperOptLoss(IHyperOptLoss):
"""
Defines the calmar loss function for hyperopt (you maybe need to add other criterias
as the max duration expected).
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
"""
# exclude the case when no trade was lost
if results.profit_percent.min() >= 0:
return MAX_LOSS
simulated_drawdowns = []
simulated_annualized_returns = []
backtest_duration_years = ((max_date-min_date).days/365)
trade_count_average_per_year = trade_count/backtest_duration_years
# add slipage to be closed to live
results['profit_percent'] -= SLIPPAGE_PERCENT
sample_size = round(trade_count_average_per_year * SIMULATION_YEAR_DURATION)
# simulate n years of run to define a median max drawdown and median annual return
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)
simulated_annualized_returns.append(randomized_result.sum()/SIMULATION_YEAR_DURATION)
abs_mediam_simulated_drawdowns = Series(simulated_drawdowns).median()
mediam_simulated_annualized_returns = Series(simulated_annualized_returns).median()
calmar_ratio = mediam_simulated_annualized_returns/abs_mediam_simulated_drawdowns
"""
Normalize loss value to be float between (-0.5, 0.5)
0 mean no profit
"""
calmar_loss = 0.5 - (norm.cdf(calmar_ratio, 0, 1.5/CALMAR_LOSS_WEIGHT))
"""
Normalize loss value to be float between (0, 0.5) :
Closed to 0 mean trade_count = NB_EXPECTED_TRADES
"""
expected_trade_loss_penalty = 1 - norm.cdf(trade_count-NB_EXPECTED_TRADES,-NB_EXPECTED_TRADES,(NB_EXPECTED_TRADES*(2/3))*EXPECTED_TRADES_WEIGHT)
# want to see the loss -> shorturl.at/DHX34
loss = calmar_loss + expected_trade_loss_penalty
# 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())