From 066524847d441203224c3834617943fe73768b2e Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Tue, 4 Feb 2020 02:02:57 +0100 Subject: [PATCH 1/2] initial push of sortino, work not done, still need own tests --- freqtrade/optimize/hyperopt_loss_sortino.py | 49 +++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 freqtrade/optimize/hyperopt_loss_sortino.py diff --git a/freqtrade/optimize/hyperopt_loss_sortino.py b/freqtrade/optimize/hyperopt_loss_sortino.py new file mode 100644 index 000000000..e84ecb402 --- /dev/null +++ b/freqtrade/optimize/hyperopt_loss_sortino.py @@ -0,0 +1,49 @@ +""" +SortinoHyperOptLoss + +This module defines the alternative HyperOptLoss class which can be used for +Hyperoptimization. +""" +from datetime import datetime + +from pandas import DataFrame +import numpy as np + +from freqtrade.optimize.hyperopt import IHyperOptLoss + + +class SortinoHyperOptLoss(IHyperOptLoss): + """ + Defines the loss function for hyperopt. + + This implementation uses the Sharpe Ratio calculation. + """ + + @staticmethod + def hyperopt_loss_function(results: DataFrame, trade_count: int, + min_date: datetime, max_date: datetime, + *args, **kwargs) -> float: + """ + Objective function, returns smaller number for more optimal results. + + Uses 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_returns_mean = total_profit.sum() / days_period + + results['downside_returns'] = 0 + results.loc[total_profit < 0, 'downside_returns'] = results['profit_percent'] + down_stdev = np.std(results['downside_returns']) + + if np.std(total_profit) != 0.0: + sortino_ratio = expected_returns_mean / down_stdev * np.sqrt(365) + else: + # Define high (negative) sharpe ratio to be clear that this is NOT optimal. + sortino_ratio = -20. + + # print(expected_returns_mean, down_stdev, sortino_ratio) + return -sortino_ratio From 0e8c569baadc9aefb80aab573def124666fb39ea Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Fri, 7 Feb 2020 01:18:15 +0100 Subject: [PATCH 2/2] Added both SortinoHyperOptLoss and SortinoHyperOptLossDaily --- freqtrade/optimize/hyperopt_loss_sortino.py | 6 +- .../optimize/hyperopt_loss_sortino_daily.py | 64 +++++++++++++++++++ 2 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 freqtrade/optimize/hyperopt_loss_sortino_daily.py diff --git a/freqtrade/optimize/hyperopt_loss_sortino.py b/freqtrade/optimize/hyperopt_loss_sortino.py index e84ecb402..83f644a43 100644 --- a/freqtrade/optimize/hyperopt_loss_sortino.py +++ b/freqtrade/optimize/hyperopt_loss_sortino.py @@ -16,7 +16,7 @@ class SortinoHyperOptLoss(IHyperOptLoss): """ Defines the loss function for hyperopt. - This implementation uses the Sharpe Ratio calculation. + This implementation uses the Sortino Ratio calculation. """ @staticmethod @@ -26,7 +26,7 @@ class SortinoHyperOptLoss(IHyperOptLoss): """ Objective function, returns smaller number for more optimal results. - Uses Sharpe Ratio calculation. + Uses Sortino Ratio calculation. """ total_profit = results["profit_percent"] days_period = (max_date - min_date).days @@ -42,7 +42,7 @@ class SortinoHyperOptLoss(IHyperOptLoss): if np.std(total_profit) != 0.0: sortino_ratio = expected_returns_mean / down_stdev * np.sqrt(365) else: - # Define high (negative) sharpe ratio to be clear that this is NOT optimal. + # Define high (negative) sortino ratio to be clear that this is NOT optimal. sortino_ratio = -20. # print(expected_returns_mean, down_stdev, sortino_ratio) diff --git a/freqtrade/optimize/hyperopt_loss_sortino_daily.py b/freqtrade/optimize/hyperopt_loss_sortino_daily.py new file mode 100644 index 000000000..b59baabcb --- /dev/null +++ b/freqtrade/optimize/hyperopt_loss_sortino_daily.py @@ -0,0 +1,64 @@ +""" +SortinoHyperOptLossDaily + +This module defines the alternative HyperOptLoss class which can be used for +Hyperoptimization. +""" +import math +from datetime import datetime + +from pandas import DataFrame, date_range + +from freqtrade.optimize.hyperopt import IHyperOptLoss + + +class SortinoHyperOptLossDaily(IHyperOptLoss): + """ + Defines the loss function for hyperopt. + + This implementation uses the Sortino Ratio calculation. + """ + + @staticmethod + def hyperopt_loss_function(results: DataFrame, trade_count: int, + min_date: datetime, max_date: datetime, + *args, **kwargs) -> float: + """ + Objective function, returns smaller number for more optimal results. + + Uses Sortino Ratio calculation. + """ + resample_freq = '1D' + slippage_per_trade_ratio = 0.0005 + days_in_year = 365 + annual_risk_free_rate = 0.0 + risk_free_rate = annual_risk_free_rate / days_in_year + + # apply slippage per trade to profit_percent + results.loc[:, 'profit_percent_after_slippage'] = \ + results['profit_percent'] - slippage_per_trade_ratio + + # create the index within the min_date and end max_date + t_index = date_range(start=min_date, end=max_date, freq=resample_freq) + + sum_daily = ( + results.resample(resample_freq, on='close_time').agg( + {"profit_percent_after_slippage": sum}).reindex(t_index).fillna(0) + ) + + total_profit = sum_daily["profit_percent_after_slippage"] - risk_free_rate + expected_returns_mean = total_profit.mean() + + results['downside_returns'] = 0 + results.loc[total_profit < 0, 'downside_returns'] = results['profit_percent'] + down_stdev = results['downside_returns'].std() + + if (down_stdev != 0.): + sortino_ratio = expected_returns_mean / down_stdev * math.sqrt(days_in_year) + else: + # Define high (negative) sortino ratio to be clear that this is NOT optimal. + sortino_ratio = -20. + + # print(t_index, sum_daily, total_profit) + # print(risk_free_rate, expected_returns_mean, down_stdev, sortino_ratio) + return -sortino_ratio