From 3845d5518641263e8bb3a6864d8043d34f29c9b8 Mon Sep 17 00:00:00 2001 From: Robert Roman Date: Tue, 21 Sep 2021 20:04:23 -0500 Subject: [PATCH 01/19] a new hyperopt loss created that uses calmar ratio This is a new hyperopt loss file that uses the Calmar Ratio. Calmar Ratio = average annual rate of return / maximum drawdown --- freqtrade/optimize/hyperopt_loss_calmar.py | 52 ++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 freqtrade/optimize/hyperopt_loss_calmar.py diff --git a/freqtrade/optimize/hyperopt_loss_calmar.py b/freqtrade/optimize/hyperopt_loss_calmar.py new file mode 100644 index 000000000..c6211cb2b --- /dev/null +++ b/freqtrade/optimize/hyperopt_loss_calmar.py @@ -0,0 +1,52 @@ +""" +CalmarHyperOptLoss + +This module defines the alternative HyperOptLoss class which can be used for +Hyperoptimization. +""" +from datetime import datetime + +import numpy as np +from pandas import DataFrame + +from freqtrade.optimize.hyperopt import IHyperOptLoss +from freqtrade.data.btanalysis import calculate_max_drawdown + + +class CalmarHyperOptLoss(IHyperOptLoss): + """ + Defines the loss function for hyperopt. + + This implementation uses the Calmar 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 Calmar Ratio calculation. + """ + total_profit = results["profit_ratio"] + 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 + + # calculate max drawdown + try: + _, _, _, high_val, low_val = calculate_max_drawdown(results) + max_drawdown = -(high_val - low_val) / high_val + except ValueError: + max_drawdown = 0 + + if max_drawdown > 0: + calmar_ratio = expected_returns_mean / max_drawdown * np.sqrt(365) + else: + calmar_ratio = -20. + + # print(calmar_ratio) + return -calmar_ratio From 3834bb86ff73794086ed188d6910bf7527c04cbf Mon Sep 17 00:00:00 2001 From: Robert Roman Date: Tue, 21 Sep 2021 20:25:17 -0500 Subject: [PATCH 02/19] updated line 42 I removed the minus sign on max drawdown. --- freqtrade/optimize/hyperopt_loss_calmar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/optimize/hyperopt_loss_calmar.py b/freqtrade/optimize/hyperopt_loss_calmar.py index c6211cb2b..8ee1a5c27 100644 --- a/freqtrade/optimize/hyperopt_loss_calmar.py +++ b/freqtrade/optimize/hyperopt_loss_calmar.py @@ -39,7 +39,7 @@ class CalmarHyperOptLoss(IHyperOptLoss): # calculate max drawdown try: _, _, _, high_val, low_val = calculate_max_drawdown(results) - max_drawdown = -(high_val - low_val) / high_val + max_drawdown = (high_val - low_val) / high_val except ValueError: max_drawdown = 0 From b946f8e7f19fb5674b77385d44c6cf266e0e37e0 Mon Sep 17 00:00:00 2001 From: Robert Roman Date: Wed, 22 Sep 2021 09:18:17 -0500 Subject: [PATCH 03/19] I sorted imports with isort --- freqtrade/optimize/hyperopt_loss_calmar.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/freqtrade/optimize/hyperopt_loss_calmar.py b/freqtrade/optimize/hyperopt_loss_calmar.py index 8ee1a5c27..866c0aa5f 100644 --- a/freqtrade/optimize/hyperopt_loss_calmar.py +++ b/freqtrade/optimize/hyperopt_loss_calmar.py @@ -7,10 +7,9 @@ Hyperoptimization. from datetime import datetime import numpy as np -from pandas import DataFrame - -from freqtrade.optimize.hyperopt import IHyperOptLoss from freqtrade.data.btanalysis import calculate_max_drawdown +from freqtrade.optimize.hyperopt import IHyperOptLoss +from pandas import DataFrame class CalmarHyperOptLoss(IHyperOptLoss): @@ -38,15 +37,14 @@ class CalmarHyperOptLoss(IHyperOptLoss): # calculate max drawdown try: - _, _, _, high_val, low_val = calculate_max_drawdown(results) + _,_,_,high_val,low_val = calculate_max_drawdown(results) max_drawdown = (high_val - low_val) / high_val except ValueError: max_drawdown = 0 - if max_drawdown > 0: + if max_drawdown != 0 and trade_count > 1000: calmar_ratio = expected_returns_mean / max_drawdown * np.sqrt(365) else: calmar_ratio = -20. - # print(calmar_ratio) return -calmar_ratio From c6b684603caffb106b9dc7f550d5caf5b980d638 Mon Sep 17 00:00:00 2001 From: Robert Roman Date: Wed, 22 Sep 2021 09:21:43 -0500 Subject: [PATCH 04/19] removed trade_count inside if statement i removed trade_count inside if statement. Even though it helps overfitting, It is not useful when running hyperopt on small datasets. --- freqtrade/optimize/hyperopt_loss_calmar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/optimize/hyperopt_loss_calmar.py b/freqtrade/optimize/hyperopt_loss_calmar.py index 866c0aa5f..b2a819444 100644 --- a/freqtrade/optimize/hyperopt_loss_calmar.py +++ b/freqtrade/optimize/hyperopt_loss_calmar.py @@ -42,7 +42,7 @@ class CalmarHyperOptLoss(IHyperOptLoss): except ValueError: max_drawdown = 0 - if max_drawdown != 0 and trade_count > 1000: + if max_drawdown != 0: calmar_ratio = expected_returns_mean / max_drawdown * np.sqrt(365) else: calmar_ratio = -20. From 3b99c84b0a879f9fbf5dae40a35a6a196c5b95d4 Mon Sep 17 00:00:00 2001 From: Robert Roman Date: Thu, 23 Sep 2021 21:31:33 -0500 Subject: [PATCH 05/19] resolved the total profit issue I resolved the total profit issue and locally ran flak8 and isort --- freqtrade/optimize/hyperopt_loss_calmar.py | 32 +++++++++++++++------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/freqtrade/optimize/hyperopt_loss_calmar.py b/freqtrade/optimize/hyperopt_loss_calmar.py index b2a819444..45a7cd7db 100644 --- a/freqtrade/optimize/hyperopt_loss_calmar.py +++ b/freqtrade/optimize/hyperopt_loss_calmar.py @@ -5,11 +5,13 @@ This module defines the alternative HyperOptLoss class which can be used for Hyperoptimization. """ from datetime import datetime +from math import sqrt as msqrt +from typing import Any, Dict + +from pandas import DataFrame -import numpy as np from freqtrade.data.btanalysis import calculate_max_drawdown from freqtrade.optimize.hyperopt import IHyperOptLoss -from pandas import DataFrame class CalmarHyperOptLoss(IHyperOptLoss): @@ -20,31 +22,41 @@ class CalmarHyperOptLoss(IHyperOptLoss): """ @staticmethod - def hyperopt_loss_function(results: DataFrame, trade_count: int, - min_date: datetime, max_date: datetime, - *args, **kwargs) -> float: + def hyperopt_loss_function( + results: DataFrame, + trade_count: int, + min_date: datetime, + max_date: datetime, + backtest_stats: Dict[str, Any], + *args, + **kwargs + ) -> float: """ Objective function, returns smaller number for more optimal results. Uses Calmar Ratio calculation. """ - total_profit = results["profit_ratio"] + total_profit = backtest_stats["profit_total"] 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 + expected_returns_mean = total_profit.sum() / days_period * 100 # calculate max drawdown try: - _,_,_,high_val,low_val = calculate_max_drawdown(results) + _, _, _, high_val, low_val = calculate_max_drawdown( + results, value_col="profit_abs" + ) max_drawdown = (high_val - low_val) / high_val except ValueError: max_drawdown = 0 if max_drawdown != 0: - calmar_ratio = expected_returns_mean / max_drawdown * np.sqrt(365) + calmar_ratio = expected_returns_mean / max_drawdown * msqrt(365) else: - calmar_ratio = -20. + # Define high (negative) calmar ratio to be clear that this is NOT optimal. + calmar_ratio = -20.0 + # print(expected_returns_mean, max_drawdown, calmar_ratio) return -calmar_ratio From 0f29cbc882806f04ba34956918cd238b73871ee1 Mon Sep 17 00:00:00 2001 From: Robert Roman Date: Thu, 23 Sep 2021 21:37:28 -0500 Subject: [PATCH 06/19] added CalmarHyperOptLoss I added CalmarHyperOptLoss to HYPEROPT_LOSS_BUILTIN variable inside constants.py file --- freqtrade/constants.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 9ca43d459..94ab5b6dd 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -24,7 +24,8 @@ ORDERTYPE_POSSIBILITIES = ['limit', 'market'] ORDERTIF_POSSIBILITIES = ['gtc', 'fok', 'ioc'] HYPEROPT_LOSS_BUILTIN = ['ShortTradeDurHyperOptLoss', 'OnlyProfitHyperOptLoss', 'SharpeHyperOptLoss', 'SharpeHyperOptLossDaily', - 'SortinoHyperOptLoss', 'SortinoHyperOptLossDaily'] + 'SortinoHyperOptLoss', 'SortinoHyperOptLossDaily', + 'CalmarHyperOptLoss'] AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList', 'AgeFilter', 'OffsetFilter', 'PerformanceFilter', 'PrecisionFilter', 'PriceFilter', 'RangeStabilityFilter', @@ -69,7 +70,9 @@ DUST_PER_COIN = { # Source files with destination directories within user-directory USER_DATA_FILES = { 'sample_strategy.py': USERPATH_STRATEGIES, + 'sample_hyperopt_advanced.py': USERPATH_HYPEROPTS, 'sample_hyperopt_loss.py': USERPATH_HYPEROPTS, + 'sample_hyperopt.py': USERPATH_HYPEROPTS, 'strategy_analysis_example.ipynb': USERPATH_NOTEBOOKS, } From b2ac039d5cbc9eec7ce15fc1b31f3a61bd968866 Mon Sep 17 00:00:00 2001 From: Robert Roman Date: Thu, 23 Sep 2021 21:46:07 -0500 Subject: [PATCH 07/19] added CalmarHyperOptLoss to HYPEROPT_LOSS_BUILTIN I added CalmarHyperOptLoss to HYPEROPT_LOSS_BUILTIN variable inside constants.py file From ca20e17d404c5aab902e765504aaa59aa42bc4a6 Mon Sep 17 00:00:00 2001 From: Robert Roman Date: Thu, 23 Sep 2021 21:48:08 -0500 Subject: [PATCH 08/19] added CalmarHyperOpt to hyperopt.md i added CalmarHyperOpt to hyperopt.md and gave a brief description inside the docs --- docs/hyperopt.md | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/docs/hyperopt.md b/docs/hyperopt.md index 09d43939a..aaad0634f 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -44,8 +44,9 @@ usage: freqtrade hyperopt [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] [--data-format-ohlcv {json,jsongz,hdf5}] [--max-open-trades INT] [--stake-amount STAKE_AMOUNT] [--fee FLOAT] - [-p PAIRS [PAIRS ...]] [--hyperopt-path PATH] - [--eps] [--dmmp] [--enable-protections] + [-p PAIRS [PAIRS ...]] [--hyperopt NAME] + [--hyperopt-path PATH] [--eps] [--dmmp] + [--enable-protections] [--dry-run-wallet DRY_RUN_WALLET] [-e INT] [--spaces {all,buy,sell,roi,stoploss,trailing,protection,default} [{all,buy,sell,roi,stoploss,trailing,protection,default} ...]] [--print-all] [--no-color] [--print-json] [-j JOBS] @@ -72,8 +73,10 @@ optional arguments: -p PAIRS [PAIRS ...], --pairs PAIRS [PAIRS ...] Limit command to these pairs. Pairs are space- separated. - --hyperopt-path PATH Specify additional lookup path for Hyperopt Loss - functions. + --hyperopt NAME Specify hyperopt class name which will be used by the + bot. + --hyperopt-path PATH Specify additional lookup path for Hyperopt and + Hyperopt Loss functions. --eps, --enable-position-stacking Allow buying the same pair multiple times (position stacking). @@ -114,7 +117,8 @@ optional arguments: Hyperopt-loss-functions are: ShortTradeDurHyperOptLoss, OnlyProfitHyperOptLoss, SharpeHyperOptLoss, SharpeHyperOptLossDaily, - SortinoHyperOptLoss, SortinoHyperOptLossDaily + SortinoHyperOptLoss, SortinoHyperOptLossDaily, + CalmarHyperOptLoss --disable-param-export Disable automatic hyperopt parameter export. @@ -518,6 +522,7 @@ Currently, the following loss functions are builtin: * `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) +* `CalmarHyperOptLoss` (optimizes Calmar Ratio calculated on trade returns relative to max drawdown) Creation of a custom loss function is covered in the [Advanced Hyperopt](advanced-hyperopt.md) part of the documentation. @@ -555,7 +560,7 @@ For example, to use one month of data, pass `--timerange 20210101-20210201` (fro Full command: ```bash -freqtrade hyperopt --strategy --timerange 20210101-20210201 +freqtrade hyperopt --hyperopt --strategy --timerange 20210101-20210201 ``` ### Running Hyperopt with Smaller Search Space @@ -677,11 +682,11 @@ If you are optimizing ROI, Freqtrade creates the 'roi' optimization hyperspace f These ranges should be sufficient in most cases. The minutes in the steps (ROI dict keys) are scaled linearly depending on the timeframe used. The ROI values in the steps (ROI dict values) are scaled logarithmically depending on the timeframe used. -If you have the `generate_roi_table()` and `roi_space()` methods in your custom hyperopt, remove them in order to utilize these adaptive ROI tables and the ROI hyperoptimization space generated by Freqtrade by default. +If you have the `generate_roi_table()` and `roi_space()` methods in your custom hyperopt file, remove them in order to utilize these adaptive ROI tables and the ROI hyperoptimization space generated by Freqtrade by default. Override the `roi_space()` method if you need components of the ROI tables to vary in other ranges. Override the `generate_roi_table()` and `roi_space()` methods and implement your own custom approach for generation of the ROI tables during hyperoptimization if you need a different structure of the ROI tables or other amount of rows (steps). -A sample for these methods can be found in the [overriding pre-defined spaces section](advanced-hyperopt.md#overriding-pre-defined-spaces). +A sample for these methods can be found in [sample_hyperopt_advanced.py](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/templates/sample_hyperopt_advanced.py). !!! Note "Reduced search space" To limit the search space further, Decimals are limited to 3 decimal places (a precision of 0.001). This is usually sufficient, every value more precise than this will usually result in overfitted results. You can however [overriding pre-defined spaces](advanced-hyperopt.md#pverriding-pre-defined-spaces) to change this to your needs. @@ -723,7 +728,7 @@ If you are optimizing stoploss values, Freqtrade creates the 'stoploss' optimiza If you have the `stoploss_space()` method in your custom hyperopt file, remove it in order to utilize Stoploss hyperoptimization space generated by Freqtrade by default. -Override the `stoploss_space()` method and define the desired range in it if you need stoploss values to vary in other range during hyperoptimization. A sample for this method can be found in the [overriding pre-defined spaces section](advanced-hyperopt.md#overriding-pre-defined-spaces). +Override the `stoploss_space()` method and define the desired range in it if you need stoploss values to vary in other range during hyperoptimization. A sample for this method can be found in [user_data/hyperopts/sample_hyperopt_advanced.py](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/templates/sample_hyperopt_advanced.py). !!! Note "Reduced search space" To limit the search space further, Decimals are limited to 3 decimal places (a precision of 0.001). This is usually sufficient, every value more precise than this will usually result in overfitted results. You can however [overriding pre-defined spaces](advanced-hyperopt.md#pverriding-pre-defined-spaces) to change this to your needs. @@ -761,10 +766,10 @@ As stated in the comment, you can also use it as the values of the corresponding If you are optimizing trailing stop values, Freqtrade creates the 'trailing' optimization hyperspace for you. By default, the `trailing_stop` parameter is always set to True in that hyperspace, the value of the `trailing_only_offset_is_reached` vary between True and False, the values of the `trailing_stop_positive` and `trailing_stop_positive_offset` parameters vary in the ranges 0.02...0.35 and 0.01...0.1 correspondingly, which is sufficient in most cases. -Override the `trailing_space()` method and define the desired range in it if you need values of the trailing stop parameters to vary in other ranges during hyperoptimization. A sample for this method can be found in the [overriding pre-defined spaces section](advanced-hyperopt.md#overriding-pre-defined-spaces). +Override the `trailing_space()` method and define the desired range in it if you need values of the trailing stop parameters to vary in other ranges during hyperoptimization. A sample for this method can be found in [user_data/hyperopts/sample_hyperopt_advanced.py](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/templates/sample_hyperopt_advanced.py). !!! Note "Reduced search space" - To limit the search space further, Decimals are limited to 3 decimal places (a precision of 0.001). This is usually sufficient, every value more precise than this will usually result in overfitted results. You can however [overriding pre-defined spaces](advanced-hyperopt.md#overriding-pre-defined-spaces) to change this to your needs. + To limit the search space further, Decimals are limited to 3 decimal places (a precision of 0.001). This is usually sufficient, every value more precise than this will usually result in overfitted results. You can however [overriding pre-defined spaces](advanced-hyperopt.md#pverriding-pre-defined-spaces) to change this to your needs. ### Reproducible results From 24baad7884b2f076f5ec0b4b12f98e20beb7b0f5 Mon Sep 17 00:00:00 2001 From: Robert Roman Date: Sat, 25 Sep 2021 16:28:36 -0500 Subject: [PATCH 09/19] Add Calmar Ratio Daily This hyper opt loss calculates the daily Calmar ratio. --- .../optimize/hyperopt_loss_calmar_daily.py | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 freqtrade/optimize/hyperopt_loss_calmar_daily.py diff --git a/freqtrade/optimize/hyperopt_loss_calmar_daily.py b/freqtrade/optimize/hyperopt_loss_calmar_daily.py new file mode 100644 index 000000000..c7651a72a --- /dev/null +++ b/freqtrade/optimize/hyperopt_loss_calmar_daily.py @@ -0,0 +1,79 @@ +""" +CalmarHyperOptLossDaily + +This module defines the alternative HyperOptLoss class which can be used for +Hyperoptimization. +""" +from datetime import datetime +from math import sqrt as msqrt +from typing import Any, Dict + +from pandas import DataFrame, date_range + +from freqtrade.optimize.hyperopt import IHyperOptLoss + + +class CalmarHyperOptLossDaily(IHyperOptLoss): + """ + Defines the loss function for hyperopt. + + This implementation uses the Calmar Ratio calculation. + """ + + @staticmethod + def hyperopt_loss_function( + results: DataFrame, + trade_count: int, + min_date: datetime, + max_date: datetime, + backtest_stats: Dict[str, Any], + *args, + **kwargs + ) -> float: + """ + Objective function, returns smaller number for more optimal results. + + Uses Calmar Ratio calculation. + """ + resample_freq = "1D" + slippage_per_trade_ratio = 0.0005 + days_in_year = 365 + + # create the index within the min_date and end max_date + t_index = date_range( + start=min_date, end=max_date, freq=resample_freq, normalize=True + ) + + # apply slippage per trade to profit_total + results.loc[:, "profit_ratio_after_slippage"] = ( + results["profit_ratio"] - slippage_per_trade_ratio + ) + + sum_daily = ( + results.resample(resample_freq, on="close_date") + .agg({"profit_ratio_after_slippage": sum}) + .reindex(t_index) + .fillna(0) + ) + + total_profit = sum_daily["profit_ratio_after_slippage"] + expected_returns_mean = total_profit.mean() * 100 + + # calculate max drawdown + try: + high_val = total_profit.max() + low_val = total_profit.min() + max_drawdown = (high_val - low_val) / high_val + + except (ValueError, ZeroDivisionError): + max_drawdown = 0 + + if max_drawdown != 0: + calmar_ratio = expected_returns_mean / max_drawdown * msqrt(days_in_year) + else: + # Define high (negative) calmar ratio to be clear that this is NOT optimal. + calmar_ratio = -20.0 + + # print(t_index, sum_daily, total_profit) + # print(expected_returns_mean, max_drawdown, calmar_ratio) + return -calmar_ratio From 89b7dfda0e4a1a6d6192a3f72e01a7224b98737d Mon Sep 17 00:00:00 2001 From: Robert Roman Date: Sat, 25 Sep 2021 16:34:41 -0500 Subject: [PATCH 10/19] Added Calmar Ratio Daily --- freqtrade/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 94ab5b6dd..42b6fccc2 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -25,7 +25,7 @@ ORDERTIF_POSSIBILITIES = ['gtc', 'fok', 'ioc'] HYPEROPT_LOSS_BUILTIN = ['ShortTradeDurHyperOptLoss', 'OnlyProfitHyperOptLoss', 'SharpeHyperOptLoss', 'SharpeHyperOptLossDaily', 'SortinoHyperOptLoss', 'SortinoHyperOptLossDaily', - 'CalmarHyperOptLoss'] + 'CalmarHyperOptLoss', 'CalmarHyperOptLossDaily'] AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList', 'AgeFilter', 'OffsetFilter', 'PerformanceFilter', 'PrecisionFilter', 'PriceFilter', 'RangeStabilityFilter', From e1036d6f58c37ec1c2d4f13626ea7b01987b97a8 Mon Sep 17 00:00:00 2001 From: Robert Roman Date: Sat, 25 Sep 2021 16:40:02 -0500 Subject: [PATCH 11/19] Added Calmar Ratio Daily to hyperopt.md file --- docs/hyperopt.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/hyperopt.md b/docs/hyperopt.md index aaad0634f..1077ff503 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -118,7 +118,7 @@ optional arguments: ShortTradeDurHyperOptLoss, OnlyProfitHyperOptLoss, SharpeHyperOptLoss, SharpeHyperOptLossDaily, SortinoHyperOptLoss, SortinoHyperOptLossDaily, - CalmarHyperOptLoss + CalmarHyperOptLoss, CalmarHyperOptLossDaily --disable-param-export Disable automatic hyperopt parameter export. @@ -523,6 +523,7 @@ Currently, the following loss functions are builtin: * `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) * `CalmarHyperOptLoss` (optimizes Calmar Ratio calculated on trade returns relative to max drawdown) +* `CalmarHyperOptLossDaily` (optimizes Calmar Ratio calculated on **daily** trade returns relative to max drawdown) Creation of a custom loss function is covered in the [Advanced Hyperopt](advanced-hyperopt.md) part of the documentation. From bc86cb3280df1b63648f72a8cbc361d136c339ab Mon Sep 17 00:00:00 2001 From: Robert Roman Date: Mon, 27 Sep 2021 11:41:38 -0500 Subject: [PATCH 12/19] updated to correct hyperopt.md file --- docs/hyperopt.md | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/docs/hyperopt.md b/docs/hyperopt.md index 1077ff503..16d7ca742 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -44,9 +44,8 @@ usage: freqtrade hyperopt [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] [--data-format-ohlcv {json,jsongz,hdf5}] [--max-open-trades INT] [--stake-amount STAKE_AMOUNT] [--fee FLOAT] - [-p PAIRS [PAIRS ...]] [--hyperopt NAME] - [--hyperopt-path PATH] [--eps] [--dmmp] - [--enable-protections] + [-p PAIRS [PAIRS ...]] [--hyperopt-path PATH] + [--eps] [--dmmp] [--enable-protections] [--dry-run-wallet DRY_RUN_WALLET] [-e INT] [--spaces {all,buy,sell,roi,stoploss,trailing,protection,default} [{all,buy,sell,roi,stoploss,trailing,protection,default} ...]] [--print-all] [--no-color] [--print-json] [-j JOBS] @@ -73,10 +72,8 @@ optional arguments: -p PAIRS [PAIRS ...], --pairs PAIRS [PAIRS ...] Limit command to these pairs. Pairs are space- separated. - --hyperopt NAME Specify hyperopt class name which will be used by the - bot. - --hyperopt-path PATH Specify additional lookup path for Hyperopt and - Hyperopt Loss functions. + --hyperopt-path PATH Specify additional lookup path for Hyperopt Loss + functions. --eps, --enable-position-stacking Allow buying the same pair multiple times (position stacking). @@ -561,7 +558,7 @@ For example, to use one month of data, pass `--timerange 20210101-20210201` (fro Full command: ```bash -freqtrade hyperopt --hyperopt --strategy --timerange 20210101-20210201 +freqtrade hyperopt --strategy --timerange 20210101-20210201 ``` ### Running Hyperopt with Smaller Search Space @@ -683,11 +680,11 @@ If you are optimizing ROI, Freqtrade creates the 'roi' optimization hyperspace f These ranges should be sufficient in most cases. The minutes in the steps (ROI dict keys) are scaled linearly depending on the timeframe used. The ROI values in the steps (ROI dict values) are scaled logarithmically depending on the timeframe used. -If you have the `generate_roi_table()` and `roi_space()` methods in your custom hyperopt file, remove them in order to utilize these adaptive ROI tables and the ROI hyperoptimization space generated by Freqtrade by default. +If you have the `generate_roi_table()` and `roi_space()` methods in your custom hyperopt, remove them in order to utilize these adaptive ROI tables and the ROI hyperoptimization space generated by Freqtrade by default. Override the `roi_space()` method if you need components of the ROI tables to vary in other ranges. Override the `generate_roi_table()` and `roi_space()` methods and implement your own custom approach for generation of the ROI tables during hyperoptimization if you need a different structure of the ROI tables or other amount of rows (steps). -A sample for these methods can be found in [sample_hyperopt_advanced.py](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/templates/sample_hyperopt_advanced.py). +A sample for these methods can be found in the [overriding pre-defined spaces section](advanced-hyperopt.md#overriding-pre-defined-spaces). !!! Note "Reduced search space" To limit the search space further, Decimals are limited to 3 decimal places (a precision of 0.001). This is usually sufficient, every value more precise than this will usually result in overfitted results. You can however [overriding pre-defined spaces](advanced-hyperopt.md#pverriding-pre-defined-spaces) to change this to your needs. @@ -729,7 +726,7 @@ If you are optimizing stoploss values, Freqtrade creates the 'stoploss' optimiza If you have the `stoploss_space()` method in your custom hyperopt file, remove it in order to utilize Stoploss hyperoptimization space generated by Freqtrade by default. -Override the `stoploss_space()` method and define the desired range in it if you need stoploss values to vary in other range during hyperoptimization. A sample for this method can be found in [user_data/hyperopts/sample_hyperopt_advanced.py](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/templates/sample_hyperopt_advanced.py). +Override the `stoploss_space()` method and define the desired range in it if you need stoploss values to vary in other range during hyperoptimization. A sample for this method can be found in the [overriding pre-defined spaces section](advanced-hyperopt.md#overriding-pre-defined-spaces). !!! Note "Reduced search space" To limit the search space further, Decimals are limited to 3 decimal places (a precision of 0.001). This is usually sufficient, every value more precise than this will usually result in overfitted results. You can however [overriding pre-defined spaces](advanced-hyperopt.md#pverriding-pre-defined-spaces) to change this to your needs. @@ -767,10 +764,10 @@ As stated in the comment, you can also use it as the values of the corresponding If you are optimizing trailing stop values, Freqtrade creates the 'trailing' optimization hyperspace for you. By default, the `trailing_stop` parameter is always set to True in that hyperspace, the value of the `trailing_only_offset_is_reached` vary between True and False, the values of the `trailing_stop_positive` and `trailing_stop_positive_offset` parameters vary in the ranges 0.02...0.35 and 0.01...0.1 correspondingly, which is sufficient in most cases. -Override the `trailing_space()` method and define the desired range in it if you need values of the trailing stop parameters to vary in other ranges during hyperoptimization. A sample for this method can be found in [user_data/hyperopts/sample_hyperopt_advanced.py](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/templates/sample_hyperopt_advanced.py). +Override the `trailing_space()` method and define the desired range in it if you need values of the trailing stop parameters to vary in other ranges during hyperoptimization. A sample for this method can be found in the [overriding pre-defined spaces section](advanced-hyperopt.md#overriding-pre-defined-spaces). !!! Note "Reduced search space" - To limit the search space further, Decimals are limited to 3 decimal places (a precision of 0.001). This is usually sufficient, every value more precise than this will usually result in overfitted results. You can however [overriding pre-defined spaces](advanced-hyperopt.md#pverriding-pre-defined-spaces) to change this to your needs. + To limit the search space further, Decimals are limited to 3 decimal places (a precision of 0.001). This is usually sufficient, every value more precise than this will usually result in overfitted results. You can however [overriding pre-defined spaces](advanced-hyperopt.md#overriding-pre-defined-spaces) to change this to your needs. ### Reproducible results From a1566fe5d70b1f526c493d007e1cbee137064685 Mon Sep 17 00:00:00 2001 From: Robert Roman Date: Mon, 27 Sep 2021 11:47:03 -0500 Subject: [PATCH 13/19] updated to latest constant.py file --- freqtrade/constants.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index bb50d385b..996e39499 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -5,7 +5,6 @@ bot constants """ from typing import List, Tuple - DEFAULT_CONFIG = 'config.json' DEFAULT_EXCHANGE = 'bittrex' PROCESS_THROTTLE_SECS = 5 # sec @@ -52,7 +51,6 @@ ENV_VAR_PREFIX = 'FREQTRADE__' NON_OPEN_EXCHANGE_STATES = ('cancelled', 'canceled', 'closed', 'expired') - # Define decimals per coin for outputs # Only used for outputs. DECIMAL_PER_COIN_FALLBACK = 3 # Should be low to avoid listing all possible FIAT's @@ -66,13 +64,10 @@ DUST_PER_COIN = { 'ETH': 0.01 } - # Source files with destination directories within user-directory USER_DATA_FILES = { 'sample_strategy.py': USERPATH_STRATEGIES, - 'sample_hyperopt_advanced.py': USERPATH_HYPEROPTS, 'sample_hyperopt_loss.py': USERPATH_HYPEROPTS, - 'sample_hyperopt.py': USERPATH_HYPEROPTS, 'strategy_analysis_example.ipynb': USERPATH_NOTEBOOKS, } @@ -195,7 +190,7 @@ CONF_SCHEMA = { 'required': ['price_side'] }, 'custom_price_max_distance_ratio': { - 'type': 'number', 'minimum': 0.0 + 'type': 'number', 'minimum': 0.0 }, 'order_types': { 'type': 'object', @@ -348,13 +343,13 @@ CONF_SCHEMA = { }, 'dataformat_ohlcv': { 'type': 'string', - 'enum': AVAILABLE_DATAHANDLERS, - 'default': 'json' + 'enum': AVAILABLE_DATAHANDLERS, + 'default': 'json' }, 'dataformat_trades': { 'type': 'string', - 'enum': AVAILABLE_DATAHANDLERS, - 'default': 'jsongz' + 'enum': AVAILABLE_DATAHANDLERS, + 'default': 'jsongz' } }, 'definitions': { From 67e9626da1a814d665307d0100864f8460e5a98a Mon Sep 17 00:00:00 2001 From: Robert Roman Date: Mon, 27 Sep 2021 12:16:57 -0500 Subject: [PATCH 14/19] fixed isort issue --- freqtrade/constants.py | 1 + 1 file changed, 1 insertion(+) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 996e39499..4a28fe90e 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -5,6 +5,7 @@ bot constants """ from typing import List, Tuple + DEFAULT_CONFIG = 'config.json' DEFAULT_EXCHANGE = 'bittrex' PROCESS_THROTTLE_SECS = 5 # sec From c3414c3b78eeddf2f1af4877a64c61643fa2a52e Mon Sep 17 00:00:00 2001 From: Robert Roman Date: Mon, 27 Sep 2021 17:32:49 -0500 Subject: [PATCH 15/19] resolved mypy error error: Signature of "hyperopt_loss_function" incompatible with supertype "IHyperOptLoss" --- freqtrade/optimize/hyperopt_loss_calmar.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/freqtrade/optimize/hyperopt_loss_calmar.py b/freqtrade/optimize/hyperopt_loss_calmar.py index 45a7cd7db..802aa949b 100644 --- a/freqtrade/optimize/hyperopt_loss_calmar.py +++ b/freqtrade/optimize/hyperopt_loss_calmar.py @@ -27,6 +27,8 @@ class CalmarHyperOptLoss(IHyperOptLoss): trade_count: int, min_date: datetime, max_date: datetime, + config: Dict, + processed: Dict[str, DataFrame], backtest_stats: Dict[str, Any], *args, **kwargs @@ -52,7 +54,7 @@ class CalmarHyperOptLoss(IHyperOptLoss): except ValueError: max_drawdown = 0 - if max_drawdown != 0: + if max_drawdown != 0 and trade_count > 2000: calmar_ratio = expected_returns_mean / max_drawdown * msqrt(365) else: # Define high (negative) calmar ratio to be clear that this is NOT optimal. From 626a40252d445d7f2108175a931820e41db4f7bf Mon Sep 17 00:00:00 2001 From: Robert Roman Date: Mon, 27 Sep 2021 17:33:29 -0500 Subject: [PATCH 16/19] resolved mypy error error: Signature of "hyperopt_loss_function" incompatible with supertype "IHyperOptLoss" --- freqtrade/optimize/hyperopt_loss_calmar_daily.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/freqtrade/optimize/hyperopt_loss_calmar_daily.py b/freqtrade/optimize/hyperopt_loss_calmar_daily.py index c7651a72a..e99bc2c99 100644 --- a/freqtrade/optimize/hyperopt_loss_calmar_daily.py +++ b/freqtrade/optimize/hyperopt_loss_calmar_daily.py @@ -26,6 +26,8 @@ class CalmarHyperOptLossDaily(IHyperOptLoss): trade_count: int, min_date: datetime, max_date: datetime, + config: Dict, + processed: Dict[str, DataFrame], backtest_stats: Dict[str, Any], *args, **kwargs From 5f309627eac656b65bfee9ce5761a138c3f809cd Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 24 Oct 2021 09:01:13 +0200 Subject: [PATCH 17/19] Update tests for Calmar ratio --- tests/optimize/test_hyperoptloss.py | 37 ++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/tests/optimize/test_hyperoptloss.py b/tests/optimize/test_hyperoptloss.py index a39190934..fd835c678 100644 --- a/tests/optimize/test_hyperoptloss.py +++ b/tests/optimize/test_hyperoptloss.py @@ -5,6 +5,7 @@ import pytest from freqtrade.exceptions import OperationalException from freqtrade.optimize.hyperopt_loss_short_trade_dur import ShortTradeDurHyperOptLoss +from freqtrade.optimize.optimize_reports import generate_strategy_stats from freqtrade.resolvers.hyperopt_resolver import HyperOptLossResolver @@ -85,6 +86,9 @@ def test_loss_calculation_has_limited_profit(hyperopt_conf, hyperopt_results) -> "SharpeHyperOptLoss", "SharpeHyperOptLossDaily", "MaxDrawDownHyperOptLoss", + "CalmarHyperOptLossDaily", + "CalmarHyperOptLoss", + ]) def test_loss_functions_better_profits(default_conf, hyperopt_results, lossfunction) -> None: results_over = hyperopt_results.copy() @@ -96,11 +100,32 @@ def test_loss_functions_better_profits(default_conf, hyperopt_results, lossfunct default_conf.update({'hyperopt_loss': lossfunction}) 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(results_over), - datetime(2019, 1, 1), datetime(2019, 5, 1)) - under = hl.hyperopt_loss_function(results_under, len(results_under), - datetime(2019, 1, 1), datetime(2019, 5, 1)) + correct = hl.hyperopt_loss_function( + hyperopt_results, + trade_count=len(hyperopt_results), + min_date=datetime(2019, 1, 1), + max_date=datetime(2019, 5, 1), + config=default_conf, + processed=None, + backtest_stats={'profit_total': hyperopt_results['profit_abs'].sum()} + ) + over = hl.hyperopt_loss_function( + results_over, + trade_count=len(results_over), + min_date=datetime(2019, 1, 1), + max_date=datetime(2019, 5, 1), + config=default_conf, + processed=None, + backtest_stats={'profit_total': results_over['profit_abs'].sum()} + ) + under = hl.hyperopt_loss_function( + results_under, + trade_count=len(results_under), + min_date=datetime(2019, 1, 1), + max_date=datetime(2019, 5, 1), + config=default_conf, + processed=None, + backtest_stats={'profit_total': results_under['profit_abs'].sum()} + ) assert over < correct assert under > correct From 88b96d5d1b1aea451331bd92a664e7a92b00dfed Mon Sep 17 00:00:00 2001 From: Robert Roman Date: Mon, 25 Oct 2021 00:45:10 -0500 Subject: [PATCH 18/19] Update hyperopt_loss_calmar.py --- freqtrade/optimize/hyperopt_loss_calmar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/optimize/hyperopt_loss_calmar.py b/freqtrade/optimize/hyperopt_loss_calmar.py index 802aa949b..ace08794a 100644 --- a/freqtrade/optimize/hyperopt_loss_calmar.py +++ b/freqtrade/optimize/hyperopt_loss_calmar.py @@ -54,7 +54,7 @@ class CalmarHyperOptLoss(IHyperOptLoss): except ValueError: max_drawdown = 0 - if max_drawdown != 0 and trade_count > 2000: + if max_drawdown != 0: calmar_ratio = expected_returns_mean / max_drawdown * msqrt(365) else: # Define high (negative) calmar ratio to be clear that this is NOT optimal. From 5cdae2ce3f79866d49a7b4aa2caf2b2924b8cffd Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 29 Oct 2021 06:42:17 +0200 Subject: [PATCH 19/19] Remove CalmarDaily hyperopt loss --- docs/hyperopt.md | 4 +- freqtrade/constants.py | 2 +- .../optimize/hyperopt_loss_calmar_daily.py | 81 ------------------- tests/optimize/test_hyperoptloss.py | 2 - 4 files changed, 2 insertions(+), 87 deletions(-) delete mode 100644 freqtrade/optimize/hyperopt_loss_calmar_daily.py diff --git a/docs/hyperopt.md b/docs/hyperopt.md index 5c98da5e2..b7b6cb772 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -116,8 +116,7 @@ optional arguments: ShortTradeDurHyperOptLoss, OnlyProfitHyperOptLoss, SharpeHyperOptLoss, SharpeHyperOptLossDaily, SortinoHyperOptLoss, SortinoHyperOptLossDaily, - CalmarHyperOptLoss, CalmarHyperOptLossDaily, - MaxDrawDownHyperOptLoss + CalmarHyperOptLoss, MaxDrawDownHyperOptLoss --disable-param-export Disable automatic hyperopt parameter export. --ignore-missing-spaces, --ignore-unparameterized-spaces @@ -526,7 +525,6 @@ Currently, the following loss functions are builtin: * `SortinoHyperOptLossDaily` - optimizes Sortino Ratio calculated on **daily** trade returns relative to **downside** standard deviation. * `MaxDrawDownHyperOptLoss` - Optimizes Maximum drawdown. * `CalmarHyperOptLoss` - Optimizes Calmar Ratio calculated on trade returns relative to max drawdown. -* `CalmarHyperOptLossDaily` Optimizes Calmar Ratio calculated on **daily** trade returns relative to max drawdown. Creation of a custom loss function is covered in the [Advanced Hyperopt](advanced-hyperopt.md) part of the documentation. diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 6b3652609..656893999 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -25,7 +25,7 @@ ORDERTIF_POSSIBILITIES = ['gtc', 'fok', 'ioc'] HYPEROPT_LOSS_BUILTIN = ['ShortTradeDurHyperOptLoss', 'OnlyProfitHyperOptLoss', 'SharpeHyperOptLoss', 'SharpeHyperOptLossDaily', 'SortinoHyperOptLoss', 'SortinoHyperOptLossDaily', - 'CalmarHyperOptLoss', 'CalmarHyperOptLossDaily', + 'CalmarHyperOptLoss', 'MaxDrawDownHyperOptLoss'] AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList', 'AgeFilter', 'OffsetFilter', 'PerformanceFilter', diff --git a/freqtrade/optimize/hyperopt_loss_calmar_daily.py b/freqtrade/optimize/hyperopt_loss_calmar_daily.py deleted file mode 100644 index e99bc2c99..000000000 --- a/freqtrade/optimize/hyperopt_loss_calmar_daily.py +++ /dev/null @@ -1,81 +0,0 @@ -""" -CalmarHyperOptLossDaily - -This module defines the alternative HyperOptLoss class which can be used for -Hyperoptimization. -""" -from datetime import datetime -from math import sqrt as msqrt -from typing import Any, Dict - -from pandas import DataFrame, date_range - -from freqtrade.optimize.hyperopt import IHyperOptLoss - - -class CalmarHyperOptLossDaily(IHyperOptLoss): - """ - Defines the loss function for hyperopt. - - This implementation uses the Calmar Ratio calculation. - """ - - @staticmethod - def hyperopt_loss_function( - results: DataFrame, - trade_count: int, - min_date: datetime, - max_date: datetime, - config: Dict, - processed: Dict[str, DataFrame], - backtest_stats: Dict[str, Any], - *args, - **kwargs - ) -> float: - """ - Objective function, returns smaller number for more optimal results. - - Uses Calmar Ratio calculation. - """ - resample_freq = "1D" - slippage_per_trade_ratio = 0.0005 - days_in_year = 365 - - # create the index within the min_date and end max_date - t_index = date_range( - start=min_date, end=max_date, freq=resample_freq, normalize=True - ) - - # apply slippage per trade to profit_total - results.loc[:, "profit_ratio_after_slippage"] = ( - results["profit_ratio"] - slippage_per_trade_ratio - ) - - sum_daily = ( - results.resample(resample_freq, on="close_date") - .agg({"profit_ratio_after_slippage": sum}) - .reindex(t_index) - .fillna(0) - ) - - total_profit = sum_daily["profit_ratio_after_slippage"] - expected_returns_mean = total_profit.mean() * 100 - - # calculate max drawdown - try: - high_val = total_profit.max() - low_val = total_profit.min() - max_drawdown = (high_val - low_val) / high_val - - except (ValueError, ZeroDivisionError): - max_drawdown = 0 - - if max_drawdown != 0: - calmar_ratio = expected_returns_mean / max_drawdown * msqrt(days_in_year) - else: - # Define high (negative) calmar ratio to be clear that this is NOT optimal. - calmar_ratio = -20.0 - - # print(t_index, sum_daily, total_profit) - # print(expected_returns_mean, max_drawdown, calmar_ratio) - return -calmar_ratio diff --git a/tests/optimize/test_hyperoptloss.py b/tests/optimize/test_hyperoptloss.py index fd835c678..e4a2eec2e 100644 --- a/tests/optimize/test_hyperoptloss.py +++ b/tests/optimize/test_hyperoptloss.py @@ -5,7 +5,6 @@ import pytest from freqtrade.exceptions import OperationalException from freqtrade.optimize.hyperopt_loss_short_trade_dur import ShortTradeDurHyperOptLoss -from freqtrade.optimize.optimize_reports import generate_strategy_stats from freqtrade.resolvers.hyperopt_resolver import HyperOptLossResolver @@ -86,7 +85,6 @@ def test_loss_calculation_has_limited_profit(hyperopt_conf, hyperopt_results) -> "SharpeHyperOptLoss", "SharpeHyperOptLossDaily", "MaxDrawDownHyperOptLoss", - "CalmarHyperOptLossDaily", "CalmarHyperOptLoss", ])