From 44d67389d2b88133c8ca745c4574b6c9fb4c3e10 Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Tue, 4 Feb 2020 02:02:57 +0100 Subject: [PATCH 01/21] 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 deb0b7ad675b16cdbbde41d289fbe45840689c9b Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Fri, 7 Feb 2020 01:18:15 +0100 Subject: [PATCH 02/21] 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 From b56a1f060318c22fa414aa0060eeb9c7c0754ac9 Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Tue, 4 Feb 2020 02:02:57 +0100 Subject: [PATCH 03/21] initial push of sortino, work not done, still need own tests --- freqtrade/optimize/hyperopt_loss_sortino.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/freqtrade/optimize/hyperopt_loss_sortino.py b/freqtrade/optimize/hyperopt_loss_sortino.py index 83f644a43..e84ecb402 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 Sortino Ratio calculation. + This implementation uses the Sharpe Ratio calculation. """ @staticmethod @@ -26,7 +26,7 @@ class SortinoHyperOptLoss(IHyperOptLoss): """ Objective function, returns smaller number for more optimal results. - Uses Sortino Ratio calculation. + Uses Sharpe 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) sortino ratio to be clear that this is NOT optimal. + # Define high (negative) sharpe ratio to be clear that this is NOT optimal. sortino_ratio = -20. # print(expected_returns_mean, down_stdev, sortino_ratio) From 728ab0ff2115dac6affd80c5b6b5f6bfb4e85a71 Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Fri, 7 Feb 2020 01:18:15 +0100 Subject: [PATCH 04/21] Added both SortinoHyperOptLoss and SortinoHyperOptLossDaily --- freqtrade/optimize/hyperopt_loss_sortino.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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) From 9bcc5d2eedb07417a0a468b3b6fa6514bc3fe2fd Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Fri, 7 Feb 2020 01:24:03 +0100 Subject: [PATCH 05/21] fixed downside_returns to read from profit_percent_after_slippage --- freqtrade/optimize/hyperopt_loss_sortino_daily.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/optimize/hyperopt_loss_sortino_daily.py b/freqtrade/optimize/hyperopt_loss_sortino_daily.py index b59baabcb..0739379f6 100644 --- a/freqtrade/optimize/hyperopt_loss_sortino_daily.py +++ b/freqtrade/optimize/hyperopt_loss_sortino_daily.py @@ -50,7 +50,7 @@ class SortinoHyperOptLossDaily(IHyperOptLoss): expected_returns_mean = total_profit.mean() results['downside_returns'] = 0 - results.loc[total_profit < 0, 'downside_returns'] = results['profit_percent'] + results.loc[total_profit < 0, 'downside_returns'] = results['profit_percent_after_slippage'] down_stdev = results['downside_returns'].std() if (down_stdev != 0.): From 951a19fb00f7e7203a430a254b583eca654cfd08 Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Fri, 7 Feb 2020 01:50:51 +0100 Subject: [PATCH 07/21] added tests for both sortino methods --- tests/optimize/test_hyperopt.py | 36 +++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index b3356bd6d..5cdbe7b5a 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -360,6 +360,42 @@ def test_sharpe_loss_daily_prefers_higher_profits(default_conf, hyperopt_results assert under > correct +def test_sortino_loss_prefers_higher_profits(default_conf, 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 + + default_conf.update({'hyperopt_loss': 'SortinoHyperOptLoss'}) + hl = HyperOptLossResolver.load_hyperoptloss(default_conf) + correct = hl.hyperopt_loss_function(hyperopt_results, len(hyperopt_results), + datetime(2019, 1, 1), datetime(2019, 5, 1)) + over = hl.hyperopt_loss_function(results_over, len(hyperopt_results), + datetime(2019, 1, 1), datetime(2019, 5, 1)) + under = hl.hyperopt_loss_function(results_under, len(hyperopt_results), + datetime(2019, 1, 1), datetime(2019, 5, 1)) + assert over < correct + assert under > correct + + +def test_sortino_loss_daily_prefers_higher_profits(default_conf, 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 + + default_conf.update({'hyperopt_loss': 'SortinoHyperOptLossDaily'}) + hl = HyperOptLossResolver.load_hyperoptloss(default_conf) + correct = hl.hyperopt_loss_function(hyperopt_results, len(hyperopt_results), + datetime(2019, 1, 1), datetime(2019, 5, 1)) + over = hl.hyperopt_loss_function(results_over, len(hyperopt_results), + datetime(2019, 1, 1), datetime(2019, 5, 1)) + under = hl.hyperopt_loss_function(results_under, len(hyperopt_results), + datetime(2019, 1, 1), datetime(2019, 5, 1)) + assert over < correct + assert under > correct + + def test_onlyprofit_loss_prefers_higher_profits(default_conf, hyperopt_results) -> None: results_over = hyperopt_results.copy() results_over['profit_percent'] = hyperopt_results['profit_percent'] * 2 From a46b7bcd6d7705bb4d2c6adc4c85ddbf8d2ad18e Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Fri, 7 Feb 2020 02:12:24 +0100 Subject: [PATCH 08/21] more fixes... --- freqtrade/optimize/hyperopt_loss_sortino_daily.py | 8 +++++--- tests/optimize/test_hyperopt.py | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/freqtrade/optimize/hyperopt_loss_sortino_daily.py b/freqtrade/optimize/hyperopt_loss_sortino_daily.py index 0739379f6..31482b981 100644 --- a/freqtrade/optimize/hyperopt_loss_sortino_daily.py +++ b/freqtrade/optimize/hyperopt_loss_sortino_daily.py @@ -47,11 +47,13 @@ class SortinoHyperOptLossDaily(IHyperOptLoss): ) total_profit = sum_daily["profit_percent_after_slippage"] - risk_free_rate + total_downside = sum_daily['downside_returns'] - risk_free_rate expected_returns_mean = total_profit.mean() - results['downside_returns'] = 0 - results.loc[total_profit < 0, 'downside_returns'] = results['profit_percent_after_slippage'] - down_stdev = results['downside_returns'].std() + sum_daily['downside_returns'] = 0 + sum_daily.loc[total_profit < 0, + 'downside_returns'] = sum_daily['profit_percent_after_slippage'] + down_stdev = total_downside.std() if (down_stdev != 0.): sortino_ratio = expected_returns_mean / down_stdev * math.sqrt(days_in_year) diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index 5cdbe7b5a..a2a98b0fb 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -380,9 +380,9 @@ def test_sortino_loss_prefers_higher_profits(default_conf, hyperopt_results) -> def test_sortino_loss_daily_prefers_higher_profits(default_conf, hyperopt_results) -> None: results_over = hyperopt_results.copy() - results_over['profit_percent'] = hyperopt_results['profit_percent'] * 2 + results_over['profit_percent_after_slippage'] = hyperopt_results['profit_percent_after_slippage'] * 2 results_under = hyperopt_results.copy() - results_under['profit_percent'] = hyperopt_results['profit_percent'] / 2 + results_under['profit_percent_after_slippage'] = hyperopt_results['profit_percent_after_slippage'] / 2 default_conf.update({'hyperopt_loss': 'SortinoHyperOptLossDaily'}) hl = HyperOptLossResolver.load_hyperoptloss(default_conf) From e8b9d88eb68cdda26b666dd42b8be39dccfc9940 Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Fri, 7 Feb 2020 02:14:12 +0100 Subject: [PATCH 09/21] moved line for total_downside --- freqtrade/optimize/hyperopt_loss_sortino_daily.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/optimize/hyperopt_loss_sortino_daily.py b/freqtrade/optimize/hyperopt_loss_sortino_daily.py index 31482b981..891e2bf1c 100644 --- a/freqtrade/optimize/hyperopt_loss_sortino_daily.py +++ b/freqtrade/optimize/hyperopt_loss_sortino_daily.py @@ -47,12 +47,12 @@ class SortinoHyperOptLossDaily(IHyperOptLoss): ) total_profit = sum_daily["profit_percent_after_slippage"] - risk_free_rate - total_downside = sum_daily['downside_returns'] - risk_free_rate expected_returns_mean = total_profit.mean() sum_daily['downside_returns'] = 0 sum_daily.loc[total_profit < 0, 'downside_returns'] = sum_daily['profit_percent_after_slippage'] + total_downside = sum_daily['downside_returns'] - risk_free_rate down_stdev = total_downside.std() if (down_stdev != 0.): From 6b279f297c1eb0e8f75957f7562fc2b6784677fe Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Fri, 7 Feb 2020 02:22:05 +0100 Subject: [PATCH 10/21] fixed test --- tests/optimize/test_hyperopt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index a2a98b0fb..5cdbe7b5a 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -380,9 +380,9 @@ def test_sortino_loss_prefers_higher_profits(default_conf, hyperopt_results) -> def test_sortino_loss_daily_prefers_higher_profits(default_conf, hyperopt_results) -> None: results_over = hyperopt_results.copy() - results_over['profit_percent_after_slippage'] = hyperopt_results['profit_percent_after_slippage'] * 2 + results_over['profit_percent'] = hyperopt_results['profit_percent'] * 2 results_under = hyperopt_results.copy() - results_under['profit_percent_after_slippage'] = hyperopt_results['profit_percent_after_slippage'] / 2 + results_under['profit_percent'] = hyperopt_results['profit_percent'] / 2 default_conf.update({'hyperopt_loss': 'SortinoHyperOptLossDaily'}) hl = HyperOptLossResolver.load_hyperoptloss(default_conf) From 9ec9a7b124a05c4f25905e8b391d06b2ec17cc26 Mon Sep 17 00:00:00 2001 From: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> Date: Sun, 9 Feb 2020 21:20:15 +0300 Subject: [PATCH 11/21] Fix t_index to be normalized --- freqtrade/optimize/hyperopt_loss_sortino_daily.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/optimize/hyperopt_loss_sortino_daily.py b/freqtrade/optimize/hyperopt_loss_sortino_daily.py index 891e2bf1c..c02f88434 100644 --- a/freqtrade/optimize/hyperopt_loss_sortino_daily.py +++ b/freqtrade/optimize/hyperopt_loss_sortino_daily.py @@ -39,7 +39,8 @@ class SortinoHyperOptLossDaily(IHyperOptLoss): 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) + t_index = date_range(start=min_date, end=max_date, freq=resample_freq, + normalize=True) sum_daily = ( results.resample(resample_freq, on='close_time').agg( From b2328cdf4fdf45c1e2f3842ec08f855fa04f1552 Mon Sep 17 00:00:00 2001 From: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> Date: Thu, 13 Feb 2020 07:07:35 +0300 Subject: [PATCH 12/21] Do not subtract risk_free_ratio twice --- freqtrade/optimize/hyperopt_loss_sortino_daily.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/optimize/hyperopt_loss_sortino_daily.py b/freqtrade/optimize/hyperopt_loss_sortino_daily.py index c02f88434..d869a4e4e 100644 --- a/freqtrade/optimize/hyperopt_loss_sortino_daily.py +++ b/freqtrade/optimize/hyperopt_loss_sortino_daily.py @@ -53,7 +53,7 @@ class SortinoHyperOptLossDaily(IHyperOptLoss): sum_daily['downside_returns'] = 0 sum_daily.loc[total_profit < 0, 'downside_returns'] = sum_daily['profit_percent_after_slippage'] - total_downside = sum_daily['downside_returns'] - risk_free_rate + total_downside = sum_daily['downside_returns'] down_stdev = total_downside.std() if (down_stdev != 0.): From 161dd1a3e60e70928d7bab25f1a8913a079420f9 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Sun, 16 Feb 2020 03:55:16 +0300 Subject: [PATCH 13/21] Rename risk_free_return to minumum_accepted_return --- freqtrade/optimize/hyperopt_loss_sortino_daily.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/freqtrade/optimize/hyperopt_loss_sortino_daily.py b/freqtrade/optimize/hyperopt_loss_sortino_daily.py index d869a4e4e..72f70e3f9 100644 --- a/freqtrade/optimize/hyperopt_loss_sortino_daily.py +++ b/freqtrade/optimize/hyperopt_loss_sortino_daily.py @@ -31,8 +31,7 @@ class SortinoHyperOptLossDaily(IHyperOptLoss): 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 + minimum_acceptable_return = 0.0 # apply slippage per trade to profit_percent results.loc[:, 'profit_percent_after_slippage'] = \ @@ -47,7 +46,7 @@ class SortinoHyperOptLossDaily(IHyperOptLoss): {"profit_percent_after_slippage": sum}).reindex(t_index).fillna(0) ) - total_profit = sum_daily["profit_percent_after_slippage"] - risk_free_rate + total_profit = sum_daily["profit_percent_after_slippage"] - minimum_acceptable_return expected_returns_mean = total_profit.mean() sum_daily['downside_returns'] = 0 From 1e84b2770cfa2e0e5e8177615dfee178ac136c1f Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Sun, 16 Feb 2020 04:10:53 +0300 Subject: [PATCH 14/21] Fix values of downside_returns --- freqtrade/optimize/hyperopt_loss_sortino_daily.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/freqtrade/optimize/hyperopt_loss_sortino_daily.py b/freqtrade/optimize/hyperopt_loss_sortino_daily.py index 72f70e3f9..26eb54c25 100644 --- a/freqtrade/optimize/hyperopt_loss_sortino_daily.py +++ b/freqtrade/optimize/hyperopt_loss_sortino_daily.py @@ -50,8 +50,7 @@ class SortinoHyperOptLossDaily(IHyperOptLoss): expected_returns_mean = total_profit.mean() sum_daily['downside_returns'] = 0 - sum_daily.loc[total_profit < 0, - 'downside_returns'] = sum_daily['profit_percent_after_slippage'] + sum_daily.loc[total_profit < 0, 'downside_returns'] = total_profit total_downside = sum_daily['downside_returns'] down_stdev = total_downside.std() From fbe5cc44da260d50db28dedf5acd12aad06913ce Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Sun, 16 Feb 2020 13:43:23 +0300 Subject: [PATCH 15/21] Use statistics.pstdev --- freqtrade/optimize/hyperopt_loss_sortino_daily.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/optimize/hyperopt_loss_sortino_daily.py b/freqtrade/optimize/hyperopt_loss_sortino_daily.py index 26eb54c25..09042cb48 100644 --- a/freqtrade/optimize/hyperopt_loss_sortino_daily.py +++ b/freqtrade/optimize/hyperopt_loss_sortino_daily.py @@ -5,6 +5,7 @@ This module defines the alternative HyperOptLoss class which can be used for Hyperoptimization. """ import math +import statistics from datetime import datetime from pandas import DataFrame, date_range @@ -52,7 +53,7 @@ class SortinoHyperOptLossDaily(IHyperOptLoss): sum_daily['downside_returns'] = 0 sum_daily.loc[total_profit < 0, 'downside_returns'] = total_profit total_downside = sum_daily['downside_returns'] - down_stdev = total_downside.std() + down_stdev = statistics.pstdev(total_downside, 0) if (down_stdev != 0.): sortino_ratio = expected_returns_mean / down_stdev * math.sqrt(days_in_year) From 42dfda92316b4678107eac65359d56d5e8b6ee0a Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Sun, 16 Feb 2020 13:46:07 +0300 Subject: [PATCH 16/21] Adjust docstring --- freqtrade/optimize/hyperopt_loss_sortino_daily.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/freqtrade/optimize/hyperopt_loss_sortino_daily.py b/freqtrade/optimize/hyperopt_loss_sortino_daily.py index 09042cb48..22cd93984 100644 --- a/freqtrade/optimize/hyperopt_loss_sortino_daily.py +++ b/freqtrade/optimize/hyperopt_loss_sortino_daily.py @@ -28,6 +28,9 @@ class SortinoHyperOptLossDaily(IHyperOptLoss): Objective function, returns smaller number for more optimal results. Uses Sortino Ratio calculation. + + Sortino Ratio calculated as described in + http://www.redrockcapital.com/Sortino__A__Sharper__Ratio_Red_Rock_Capital.pdf """ resample_freq = '1D' slippage_per_trade_ratio = 0.0005 From 674898bd32eede682dad5a688ee1428b89a8805c Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Sun, 16 Feb 2020 15:26:40 +0300 Subject: [PATCH 17/21] Fix usage of vars in the commented out line --- freqtrade/optimize/hyperopt_loss_sortino_daily.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/optimize/hyperopt_loss_sortino_daily.py b/freqtrade/optimize/hyperopt_loss_sortino_daily.py index 22cd93984..0f81ffca5 100644 --- a/freqtrade/optimize/hyperopt_loss_sortino_daily.py +++ b/freqtrade/optimize/hyperopt_loss_sortino_daily.py @@ -65,5 +65,5 @@ class SortinoHyperOptLossDaily(IHyperOptLoss): sortino_ratio = -20. # print(t_index, sum_daily, total_profit) - # print(risk_free_rate, expected_returns_mean, down_stdev, sortino_ratio) + # print(minimum_acceptable_return, expected_returns_mean, down_stdev, sortino_ratio) return -sortino_ratio From df26c357d293f69e57cffc4de60284776d183026 Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Wed, 19 Feb 2020 01:31:25 +0100 Subject: [PATCH 18/21] doc updates --- docs/bot-usage.md | 12 +++++++----- docs/hyperopt.md | 14 ++++++++------ freqtrade/commands/cli_options.py | 3 ++- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/docs/bot-usage.md b/docs/bot-usage.md index 56e6008a1..e0d96a8c2 100644 --- a/docs/bot-usage.md +++ b/docs/bot-usage.md @@ -270,7 +270,7 @@ Check the corresponding [Data Downloading](data-download.md) section for more de ## Hyperopt commands To optimize your strategy, you can use hyperopt parameter hyperoptimization -to find optimal parameter values for your stategy. +to find optimal parameter values for your strategy. ``` usage: freqtrade hyperopt [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] @@ -318,7 +318,7 @@ optional arguments: --print-all Print all results, not only the best ones. --no-color Disable colorization of hyperopt results. May be useful if you are redirecting output to a file. - --print-json Print best result detailization in JSON format. + --print-json Print best results in JSON format. -j JOBS, --job-workers JOBS The number of concurrently running jobs for hyperoptimization (hyperopt worker processes). If -1 @@ -336,9 +336,11 @@ optional arguments: class (IHyperOptLoss). Different functions can generate completely different results, since the target for optimization is different. Built-in - Hyperopt-loss-functions are: DefaultHyperOptLoss, - OnlyProfitHyperOptLoss, SharpeHyperOptLoss, - SharpeHyperOptLossDaily (default: `DefaultHyperOptLoss`). + Hyperopt-loss-functions are: + DefaultHyperOptLoss, OnlyProfitHyperOptLoss, + SharpeHyperOptLoss, SharpeHyperOptLossDaily, + SortinoHyperOptLoss, SortinoHyperOptLossDaily. + (default: `DefaultHyperOptLoss`). Common arguments: -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). diff --git a/docs/hyperopt.md b/docs/hyperopt.md index 3e10f66da..3fb7ce7ba 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -81,11 +81,11 @@ There are two places you need to change in your hyperopt file to add a new buy h There you have two different types of indicators: 1. `guards` and 2. `triggers`. 1. Guards are conditions like "never buy if ADX < 10", or never buy if current price is over EMA10. -2. Triggers are ones that actually trigger buy in specific moment, like "buy when EMA5 crosses over EMA10" or "buy when close price touches lower bollinger band". +2. Triggers are ones that actually trigger buy in specific moment, like "buy when EMA5 crosses over EMA10" or "buy when close price touches lower Bollinger band". Hyperoptimization will, for each eval round, pick one trigger and possibly multiple guards. The constructed strategy will be something like -"*buy exactly when close price touches lower bollinger band, BUT only if +"*buy exactly when close price touches lower Bollinger band, BUT only if ADX > 10*". If you have updated the buy strategy, i.e. changed the contents of @@ -172,7 +172,7 @@ So let's write the buy strategy using these values: Hyperopting will now call this `populate_buy_trend` as many times you ask it (`epochs`) with different value combinations. It will then use the given historical data and make buys based on the buy signals generated with the above function and based on the results -it will end with telling you which paramter combination produced the best profits. +it will end with telling you which parameter combination produced the best profits. The above setup expects to find ADX, RSI and Bollinger Bands in the populated indicators. When you want to test an indicator that isn't used by the bot currently, remember to @@ -191,8 +191,10 @@ Currently, the following loss functions are builtin: * `DefaultHyperOptLoss` (default legacy Freqtrade hyperoptimization loss function) * `OnlyProfitHyperOptLoss` (which takes only amount of profit into consideration) -* `SharpeHyperOptLoss` (optimizes Sharpe Ratio calculated on the trade returns) -* `SharpeHyperOptLossDaily` (optimizes Sharpe Ratio calculated on daily trade returns) +* `SharpeHyperOptLoss` (optimizes Sharpe Ratio calculated on trade returns relative to **upside** standard deviation) +* `SharpeHyperOptLossDaily` (optimizes Sharpe Ratio calculated on **daily** trade returns relative to **upside** standard deviation) +* `SortinoHyperOptLoss` (optimizes Sortino Ratio calculated on trade returns relative to **downside** standard deviation) +* `SortinoHyperOptLossDaily` (optimizes Sortino Ratio calculated on **daily** trade returns relative to **downside** standard deviation) Creation of a custom loss function is covered in the [Advanced Hyperopt](advanced-hyperopt.md) part of the documentation. @@ -272,7 +274,7 @@ In some situations, you may need to run Hyperopt (and Backtesting) with the By default, hyperopt emulates the behavior of the Freqtrade Live Run/Dry Run, where only one open trade is allowed for every traded pair. The total number of trades open for all pairs is also limited by the `max_open_trades` setting. During Hyperopt/Backtesting this may lead to -some potential trades to be hidden (or masked) by previosly open trades. +some potential trades to be hidden (or masked) by previously open trades. The `--eps`/`--enable-position-stacking` argument allows emulation of buying the same pair multiple times, while `--dmmp`/`--disable-max-market-positions` disables applying `max_open_trades` diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py index 6d8d13129..5faff1a97 100644 --- a/freqtrade/commands/cli_options.py +++ b/freqtrade/commands/cli_options.py @@ -256,7 +256,8 @@ AVAILABLE_CLI_OPTIONS = { help='Specify the class name of the hyperopt loss function class (IHyperOptLoss). ' 'Different functions can generate completely different results, ' 'since the target for optimization is different. Built-in Hyperopt-loss-functions are: ' - 'DefaultHyperOptLoss, OnlyProfitHyperOptLoss, SharpeHyperOptLoss, SharpeHyperOptLossDaily.' + 'DefaultHyperOptLoss, OnlyProfitHyperOptLoss, SharpeHyperOptLoss, SharpeHyperOptLossDaily, ' + 'SortinoHyperOptLoss, SortinoHyperOptLossDaily.' '(default: `%(default)s`).', metavar='NAME', default=constants.DEFAULT_HYPEROPT_LOSS, From 41b4fa3b7f93018a5c01042b8aa8734108e57407 Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Wed, 19 Feb 2020 02:59:51 +0100 Subject: [PATCH 19/21] fixed two more typos --- docs/hyperopt.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/hyperopt.md b/docs/hyperopt.md index b471f849e..1f9907b83 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -31,9 +31,9 @@ This will create a new hyperopt file from a template, which will be located unde Depending on the space you want to optimize, only some of the below are required: * fill `buy_strategy_generator` - for buy signal optimization -* fill `indicator_space` - for buy signal optimzation +* fill `indicator_space` - for buy signal optimization * fill `sell_strategy_generator` - for sell signal optimization -* fill `sell_indicator_space` - for sell signal optimzation +* fill `sell_indicator_space` - for sell signal optimization !!! Note `populate_indicators` needs to create all indicators any of thee spaces may use, otherwise hyperopt will not work. From 09a1c9eed6b629fe9f4502cbcf805b8e78b8da13 Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Wed, 19 Feb 2020 22:25:34 +0100 Subject: [PATCH 20/21] fixed docs description of hyperopts --- docs/hyperopt.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/hyperopt.md b/docs/hyperopt.md index 1f9907b83..9bc5888ce 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -191,8 +191,8 @@ Currently, the following loss functions are builtin: * `DefaultHyperOptLoss` (default legacy Freqtrade hyperoptimization loss function) * `OnlyProfitHyperOptLoss` (which takes only amount of profit into consideration) -* `SharpeHyperOptLoss` (optimizes Sharpe Ratio calculated on trade returns relative to **upside** standard deviation) -* `SharpeHyperOptLossDaily` (optimizes Sharpe Ratio calculated on **daily** trade returns relative to **upside** standard deviation) +* `SharpeHyperOptLoss` (optimizes Sharpe Ratio calculated on trade returns relative to standard deviation) +* `SharpeHyperOptLossDaily` (optimizes Sharpe Ratio calculated on **daily** trade returns relative to standard deviation) * `SortinoHyperOptLoss` (optimizes Sortino Ratio calculated on trade returns relative to **downside** standard deviation) * `SortinoHyperOptLossDaily` (optimizes Sortino Ratio calculated on **daily** trade returns relative to **downside** standard deviation) From bee8e92f0287aec18b77e936a5a5d1771aa869a0 Mon Sep 17 00:00:00 2001 From: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> Date: Fri, 28 Feb 2020 23:50:25 +0300 Subject: [PATCH 21/21] Final changes, use sqrt i.o. statistics.pstdev --- freqtrade/optimize/hyperopt_loss_sortino_daily.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/freqtrade/optimize/hyperopt_loss_sortino_daily.py b/freqtrade/optimize/hyperopt_loss_sortino_daily.py index 0f81ffca5..16dc26142 100644 --- a/freqtrade/optimize/hyperopt_loss_sortino_daily.py +++ b/freqtrade/optimize/hyperopt_loss_sortino_daily.py @@ -5,7 +5,6 @@ This module defines the alternative HyperOptLoss class which can be used for Hyperoptimization. """ import math -import statistics from datetime import datetime from pandas import DataFrame, date_range @@ -56,7 +55,9 @@ class SortinoHyperOptLossDaily(IHyperOptLoss): sum_daily['downside_returns'] = 0 sum_daily.loc[total_profit < 0, 'downside_returns'] = total_profit total_downside = sum_daily['downside_returns'] - down_stdev = statistics.pstdev(total_downside, 0) + # Here total_downside contains min(0, P - MAR) values, + # where P = sum_daily["profit_percent_after_slippage"] + down_stdev = math.sqrt((total_downside**2).sum() / len(total_downside)) if (down_stdev != 0.): sortino_ratio = expected_returns_mean / down_stdev * math.sqrt(days_in_year)