From 44d67389d2b88133c8ca745c4574b6c9fb4c3e10 Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Tue, 4 Feb 2020 02:02:57 +0100 Subject: [PATCH 001/202] 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 002/202] 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 003/202] 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 004/202] 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 005/202] 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 be34dc463b31e4698638d72dc2c295f2c3ec91f5 Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Fri, 7 Feb 2020 01:30:03 +0100 Subject: [PATCH 006/202] fixed bad commit From 951a19fb00f7e7203a430a254b583eca654cfd08 Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Fri, 7 Feb 2020 01:50:51 +0100 Subject: [PATCH 007/202] 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 008/202] 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 009/202] 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 010/202] 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 011/202] 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 012/202] 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 013/202] 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 014/202] 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 015/202] 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 016/202] 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 017/202] 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 2058b492eb2b668616f06d0d001e01ffca17e38f Mon Sep 17 00:00:00 2001 From: Fredrik Rydin Date: Tue, 18 Feb 2020 22:46:53 +0100 Subject: [PATCH 018/202] Added function to print hyperopt-list as table using tabulate --- docs/utils.md | 1 + freqtrade/commands/arguments.py | 3 +- freqtrade/commands/cli_options.py | 6 +++ freqtrade/commands/hyperopt_commands.py | 36 ++++++++++-------- freqtrade/configuration/configuration.py | 3 ++ freqtrade/optimize/hyperopt.py | 47 +++++++++++++++++++++++- tests/commands/test_commands.py | 16 ++++++++ 7 files changed, 95 insertions(+), 17 deletions(-) diff --git a/docs/utils.md b/docs/utils.md index abb7fd0db..6ca0b7920 100644 --- a/docs/utils.md +++ b/docs/utils.md @@ -440,6 +440,7 @@ optional arguments: --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-table Print results in table format. --no-details Do not print best epoch details. Common arguments: diff --git a/freqtrade/commands/arguments.py b/freqtrade/commands/arguments.py index fe6f49039..0f6478a60 100644 --- a/freqtrade/commands/arguments.py +++ b/freqtrade/commands/arguments.py @@ -66,7 +66,8 @@ ARGS_HYPEROPT_LIST = ["hyperopt_list_best", "hyperopt_list_profitable", "hyperopt_list_min_avg_time", "hyperopt_list_max_avg_time", "hyperopt_list_min_avg_profit", "hyperopt_list_max_avg_profit", "hyperopt_list_min_total_profit", "hyperopt_list_max_total_profit", - "print_colorized", "print_json", "hyperopt_list_no_details"] + "print_colorized", "print_json", "print_table", + "hyperopt_list_no_details"] ARGS_HYPEROPT_SHOW = ["hyperopt_list_best", "hyperopt_list_profitable", "hyperopt_show_index", "print_json", "hyperopt_show_no_header"] diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py index 1776955b1..9efc1151a 100644 --- a/freqtrade/commands/cli_options.py +++ b/freqtrade/commands/cli_options.py @@ -220,6 +220,12 @@ AVAILABLE_CLI_OPTIONS = { action='store_true', default=False, ), + "print_table": Arg( + '--print-table', + help='Print results in table format.', + action='store_true', + default=False, + ), "hyperopt_jobs": Arg( '-j', '--job-workers', help='The number of concurrently running jobs for hyperoptimization ' diff --git a/freqtrade/commands/hyperopt_commands.py b/freqtrade/commands/hyperopt_commands.py index 8c1c80d98..88cd79468 100755 --- a/freqtrade/commands/hyperopt_commands.py +++ b/freqtrade/commands/hyperopt_commands.py @@ -21,6 +21,7 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None: print_colorized = config.get('print_colorized', False) print_json = config.get('print_json', False) + print_table = config.get('print_table', False) no_details = config.get('hyperopt_list_no_details', False) no_header = False @@ -52,14 +53,20 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None: if print_colorized: colorama_init(autoreset=True) - try: - # Human-friendly indexes used here (starting from 1) - for val in trials[epoch_start:epoch_stop]: - Hyperopt.print_results_explanation(val, total_epochs, - not filteroptions['only_best'], print_colorized) - - except KeyboardInterrupt: - print('User interrupted..') + if print_table: + try: + Hyperopt.print_result_table(config, trials, total_epochs, + not filteroptions['only_best'], print_colorized) + except KeyboardInterrupt: + print('User interrupted..') + else: + try: + # Human-friendly indexes used here (starting from 1) + for val in trials[epoch_start:epoch_stop]: + Hyperopt.print_results_explanation(val, total_epochs, + not filteroptions['only_best'], print_colorized) + except KeyboardInterrupt: + print('User interrupted..') if trials and not no_details: sorted_trials = sorted(trials, key=itemgetter('loss')) @@ -75,6 +82,12 @@ def start_hyperopt_show(args: Dict[str, Any]) -> None: config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) + print_json = config.get('print_json', False) + no_header = config.get('hyperopt_show_no_header', False) + trials_file = (config['user_data_dir'] / + 'hyperopt_results' / 'hyperopt_results.pickle') + n = config.get('hyperopt_show_index', -1) + filteroptions = { 'only_best': config.get('hyperopt_list_best', False), 'only_profitable': config.get('hyperopt_list_profitable', False), @@ -87,10 +100,6 @@ def start_hyperopt_show(args: Dict[str, Any]) -> None: 'filter_min_total_profit': config.get('hyperopt_list_min_total_profit', None), 'filter_max_total_profit': config.get('hyperopt_list_max_total_profit', None) } - no_header = config.get('hyperopt_show_no_header', False) - - trials_file = (config['user_data_dir'] / - 'hyperopt_results' / 'hyperopt_results.pickle') # Previous evaluations trials = Hyperopt.load_previous_results(trials_file) @@ -99,7 +108,6 @@ def start_hyperopt_show(args: Dict[str, Any]) -> None: trials = _hyperopt_filter_trials(trials, filteroptions) trials_epochs = len(trials) - n = config.get('hyperopt_show_index', -1) if n > trials_epochs: raise OperationalException( f"The index of the epoch to show should be less than {trials_epochs + 1}.") @@ -111,8 +119,6 @@ def start_hyperopt_show(args: Dict[str, Any]) -> None: if n > 0: n -= 1 - print_json = config.get('print_json', False) - if trials: val = trials[n] Hyperopt.print_epoch_details(val, total_epochs, print_json, no_header, diff --git a/freqtrade/configuration/configuration.py b/freqtrade/configuration/configuration.py index c2613ba99..0adfe03e7 100644 --- a/freqtrade/configuration/configuration.py +++ b/freqtrade/configuration/configuration.py @@ -286,6 +286,9 @@ class Configuration: self._args_to_config(config, argname='print_json', logstring='Parameter --print-json detected ...') + self._args_to_config(config, argname='print_table', + logstring='Parameter --print-table detected: {}') + self._args_to_config(config, argname='hyperopt_jobs', logstring='Parameter -j/--job-workers detected: {}') diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index ff6e7f3bc..72b3516c7 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -20,7 +20,8 @@ from colorama import Fore, Style from colorama import init as colorama_init from joblib import (Parallel, cpu_count, delayed, dump, load, wrap_non_picklable_objects) -from pandas import DataFrame +from pandas import DataFrame, json_normalize, isna +from tabulate import tabulate from freqtrade.data.history import get_timerange, trim_dataframe from freqtrade.exceptions import OperationalException @@ -295,6 +296,50 @@ class Hyperopt: f"{results['results_explanation']} " + f"Objective: {results['loss']:.5f}") + @staticmethod + def print_result_table(config: dict, results: list, total_epochs: int, highlight_best: bool, + print_colorized: bool) -> None: + """ + Log result table + """ + if not results: + return + + trials = json_normalize(results, max_level=1) + trials = trials[['is_best', 'current_epoch', + 'results_metrics.trade_count', 'results_metrics.avg_profit', + 'results_metrics.total_profit', 'results_metrics.profit', + 'results_metrics.duration', 'loss']] + trials.columns = ['Best', 'Epoch', 'Trades', 'Avg profit', 'Total profit', + 'Profit', 'Avg duration', 'Objective'] + + trials['Best'] = trials['Best'].apply(lambda x: '*' if x else '') + trials['Objective'] = trials['Objective'].astype(str) + + if print_colorized: + for i in range(len(trials)): + if trials.loc[i]['Total profit'] > 0: + trials.at[i, 'Best'] = Fore.GREEN + trials.loc[i]['Best'] + trials.at[i, 'Objective'] = "{}{}".format(trials.loc[i]['Objective'], + Fore.RESET) + if '*' in trials.loc[i]['Best'] and highlight_best: + trials.at[i, 'Best'] = Style.BRIGHT + trials.loc[i]['Best'] + trials.at[i, 'Objective'] = "{}{}".format(trials.loc[i]['Objective'], + Style.RESET_ALL) + + trials['Epoch'] = trials['Epoch'].apply( + lambda x: "{}/{}".format(x, total_epochs)) + trials['Avg profit'] = trials['Avg profit'].apply( + lambda x: '{:,.2f}%'.format(x) if not isna(x) else x) + trials['Profit'] = trials['Profit'].apply( + lambda x: '{:,.2f}%'.format(x) if not isna(x) else x) + trials['Total profit'] = trials['Total profit'].apply( + lambda x: '{: 11.8f} '.format(x) + config['stake_currency'] if not isna(x) else x) + trials['Avg duration'] = trials['Avg duration'].apply( + lambda x: '{:,.1f}m'.format(x) if not isna(x) else x) + + print(tabulate(trials.to_dict(orient='list'), headers='keys', tablefmt='psql')) + def has_space(self, space: str) -> bool: """ Tell if the space value is contained in the configuration diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index ee1db5db5..9ecd990ec 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -893,6 +893,22 @@ def test_hyperopt_list(mocker, capsys, hyperopt_results): assert all(x not in captured.out for x in [" 1/12", " 3/12", " 4/12", " 5/12", " 7/12", " 8/12" " 9/12", " 10/12", " 11/12", " 12/12"]) + args = [ + "hyperopt-list", + "--profitable", + "--no-details", + "--print-table", + "--max-trades", "20" + ] + pargs = get_args(args) + pargs['config'] = None + start_hyperopt_list(pargs) + captured = capsys.readouterr() + assert all(x in captured.out + for x in [" 2/12", " 10/12"]) + assert all(x not in captured.out + for x in [" 1/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12", " 8/12", " 9/12", + " 11/12", " 12/12"]) def test_hyperopt_show(mocker, capsys, hyperopt_results): From 585545405dea4d34bc1ce6d8296ceb38861a7b85 Mon Sep 17 00:00:00 2001 From: Fredrik Rydin Date: Wed, 19 Feb 2020 00:51:44 +0100 Subject: [PATCH 019/202] Changed tests --- tests/commands/test_commands.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index 9ecd990ec..9c233019c 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -895,20 +895,20 @@ def test_hyperopt_list(mocker, capsys, hyperopt_results): " 9/12", " 10/12", " 11/12", " 12/12"]) args = [ "hyperopt-list", - "--profitable", "--no-details", "--print-table", - "--max-trades", "20" + "--min-trades", "100", + "--print-json" ] pargs = get_args(args) pargs['config'] = None start_hyperopt_list(pargs) captured = capsys.readouterr() assert all(x in captured.out - for x in [" 2/12", " 10/12"]) + for x in [" 3/12", " 7/12", " 9/12", " 11/12"]) assert all(x not in captured.out - for x in [" 1/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12", " 8/12", " 9/12", - " 11/12", " 12/12"]) + for x in [" 1/12", " 2/12", " 4/12", " 5/12", " 6/12", " 8/12", " 10/12" + " 12/12"]) def test_hyperopt_show(mocker, capsys, hyperopt_results): From df26c357d293f69e57cffc4de60284776d183026 Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Wed, 19 Feb 2020 01:31:25 +0100 Subject: [PATCH 020/202] 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 021/202] 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 022/202] 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 e7b12704dec4c8265e5adf51dfb3d7f36367adae Mon Sep 17 00:00:00 2001 From: Fredrik81 Date: Thu, 20 Feb 2020 19:12:55 +0100 Subject: [PATCH 023/202] Added test for details --- tests/commands/test_commands.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index 9c233019c..dfaa936ed 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -778,6 +778,19 @@ def test_hyperopt_list(mocker, capsys, hyperopt_results): assert all(x not in captured.out for x in [" 1/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12", " 8/12", " 9/12", " 11/12", " 12/12"]) + args = [ + "hyperopt-list", + "--profitable" + ] + pargs = get_args(args) + pargs['config'] = None + start_hyperopt_list(pargs) + captured = capsys.readouterr() + assert all(x in captured.out + for x in [" 2/12", " 10/12", "Best result:", "Buy hyperspace params", "Sell hyperspace params", "ROI table", "Stoploss"]) + assert all(x not in captured.out + for x in [" 1/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12", " 8/12", " 9/12", + " 11/12", " 12/12"]) args = [ "hyperopt-list", "--no-details", From 09226fd5d5e378e9f7ee11382528ec6c06ac7571 Mon Sep 17 00:00:00 2001 From: Fredrik81 Date: Thu, 20 Feb 2020 19:18:42 +0100 Subject: [PATCH 024/202] PEP8 correction --- tests/commands/test_commands.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index dfaa936ed..ceae6f372 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -787,7 +787,8 @@ def test_hyperopt_list(mocker, capsys, hyperopt_results): start_hyperopt_list(pargs) captured = capsys.readouterr() assert all(x in captured.out - for x in [" 2/12", " 10/12", "Best result:", "Buy hyperspace params", "Sell hyperspace params", "ROI table", "Stoploss"]) + for x in [" 2/12", " 10/12", "Best result:", "Buy hyperspace params", + "Sell hyperspace params", "ROI table", "Stoploss"]) assert all(x not in captured.out for x in [" 1/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12", " 8/12", " 9/12", " 11/12", " 12/12"]) From 7eb62ed32e7acc7004821a9213c7752fe31f1f9c Mon Sep 17 00:00:00 2001 From: Fredrik81 Date: Mon, 24 Feb 2020 00:33:01 +0100 Subject: [PATCH 025/202] Remove old print option for hyperopt-list and made table as default --- docs/utils.md | 1 - freqtrade/commands/arguments.py | 3 +-- freqtrade/commands/cli_options.py | 6 ------ freqtrade/commands/hyperopt_commands.py | 23 +++++------------------ freqtrade/configuration/configuration.py | 3 --- tests/commands/test_commands.py | 16 ---------------- 6 files changed, 6 insertions(+), 46 deletions(-) diff --git a/docs/utils.md b/docs/utils.md index 6ca0b7920..abb7fd0db 100644 --- a/docs/utils.md +++ b/docs/utils.md @@ -440,7 +440,6 @@ optional arguments: --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-table Print results in table format. --no-details Do not print best epoch details. Common arguments: diff --git a/freqtrade/commands/arguments.py b/freqtrade/commands/arguments.py index 0f6478a60..fe6f49039 100644 --- a/freqtrade/commands/arguments.py +++ b/freqtrade/commands/arguments.py @@ -66,8 +66,7 @@ ARGS_HYPEROPT_LIST = ["hyperopt_list_best", "hyperopt_list_profitable", "hyperopt_list_min_avg_time", "hyperopt_list_max_avg_time", "hyperopt_list_min_avg_profit", "hyperopt_list_max_avg_profit", "hyperopt_list_min_total_profit", "hyperopt_list_max_total_profit", - "print_colorized", "print_json", "print_table", - "hyperopt_list_no_details"] + "print_colorized", "print_json", "hyperopt_list_no_details"] ARGS_HYPEROPT_SHOW = ["hyperopt_list_best", "hyperopt_list_profitable", "hyperopt_show_index", "print_json", "hyperopt_show_no_header"] diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py index 9efc1151a..1776955b1 100644 --- a/freqtrade/commands/cli_options.py +++ b/freqtrade/commands/cli_options.py @@ -220,12 +220,6 @@ AVAILABLE_CLI_OPTIONS = { action='store_true', default=False, ), - "print_table": Arg( - '--print-table', - help='Print results in table format.', - action='store_true', - default=False, - ), "hyperopt_jobs": Arg( '-j', '--job-workers', help='The number of concurrently running jobs for hyperoptimization ' diff --git a/freqtrade/commands/hyperopt_commands.py b/freqtrade/commands/hyperopt_commands.py index 88cd79468..ccaa59e54 100755 --- a/freqtrade/commands/hyperopt_commands.py +++ b/freqtrade/commands/hyperopt_commands.py @@ -21,7 +21,6 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None: print_colorized = config.get('print_colorized', False) print_json = config.get('print_json', False) - print_table = config.get('print_table', False) no_details = config.get('hyperopt_list_no_details', False) no_header = False @@ -47,26 +46,14 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None: trials = _hyperopt_filter_trials(trials, filteroptions) - # TODO: fetch the interval for epochs to print from the cli option - epoch_start, epoch_stop = 0, None - if print_colorized: colorama_init(autoreset=True) - if print_table: - try: - Hyperopt.print_result_table(config, trials, total_epochs, - not filteroptions['only_best'], print_colorized) - except KeyboardInterrupt: - print('User interrupted..') - else: - try: - # Human-friendly indexes used here (starting from 1) - for val in trials[epoch_start:epoch_stop]: - Hyperopt.print_results_explanation(val, total_epochs, - not filteroptions['only_best'], print_colorized) - except KeyboardInterrupt: - print('User interrupted..') + try: + Hyperopt.print_result_table(config, trials, total_epochs, + not filteroptions['only_best'], print_colorized) + except KeyboardInterrupt: + print('User interrupted..') if trials and not no_details: sorted_trials = sorted(trials, key=itemgetter('loss')) diff --git a/freqtrade/configuration/configuration.py b/freqtrade/configuration/configuration.py index 0adfe03e7..c2613ba99 100644 --- a/freqtrade/configuration/configuration.py +++ b/freqtrade/configuration/configuration.py @@ -286,9 +286,6 @@ class Configuration: self._args_to_config(config, argname='print_json', logstring='Parameter --print-json detected ...') - self._args_to_config(config, argname='print_table', - logstring='Parameter --print-table detected: {}') - self._args_to_config(config, argname='hyperopt_jobs', logstring='Parameter -j/--job-workers detected: {}') diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index ceae6f372..995b504c5 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -907,22 +907,6 @@ def test_hyperopt_list(mocker, capsys, hyperopt_results): assert all(x not in captured.out for x in [" 1/12", " 3/12", " 4/12", " 5/12", " 7/12", " 8/12" " 9/12", " 10/12", " 11/12", " 12/12"]) - args = [ - "hyperopt-list", - "--no-details", - "--print-table", - "--min-trades", "100", - "--print-json" - ] - pargs = get_args(args) - pargs['config'] = None - start_hyperopt_list(pargs) - captured = capsys.readouterr() - assert all(x in captured.out - for x in [" 3/12", " 7/12", " 9/12", " 11/12"]) - assert all(x not in captured.out - for x in [" 1/12", " 2/12", " 4/12", " 5/12", " 6/12", " 8/12", " 10/12" - " 12/12"]) def test_hyperopt_show(mocker, capsys, hyperopt_results): From 23bf135b8aeb704c64e775dba8a12b166a692a41 Mon Sep 17 00:00:00 2001 From: Fredrik81 Date: Mon, 24 Feb 2020 11:01:14 +0100 Subject: [PATCH 026/202] Alignment of table content, changed coloring, changed 'Best' column to show if it's initial_point or best --- freqtrade/optimize/hyperopt.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 72b3516c7..7ff5c3500 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -306,15 +306,18 @@ class Hyperopt: return trials = json_normalize(results, max_level=1) - trials = trials[['is_best', 'current_epoch', - 'results_metrics.trade_count', 'results_metrics.avg_profit', - 'results_metrics.total_profit', 'results_metrics.profit', - 'results_metrics.duration', 'loss']] + trials['Best'] = '' + trials = trials[['Best', 'current_epoch', 'results_metrics.trade_count', + 'results_metrics.avg_profit', 'results_metrics.total_profit', + 'results_metrics.profit', 'results_metrics.duration', + 'loss', 'is_initial_point', 'is_best']] trials.columns = ['Best', 'Epoch', 'Trades', 'Avg profit', 'Total profit', - 'Profit', 'Avg duration', 'Objective'] + 'Profit', 'Avg duration', 'Objective', 'is_initial_point', 'is_best'] - trials['Best'] = trials['Best'].apply(lambda x: '*' if x else '') + trials.loc[trials['is_initial_point'], 'Best'] = '*' + trials.loc[trials['is_best'], 'Best'] = 'Best' trials['Objective'] = trials['Objective'].astype(str) + trials = trials.drop(columns=['is_initial_point', 'is_best']) if print_colorized: for i in range(len(trials)): @@ -322,10 +325,10 @@ class Hyperopt: trials.at[i, 'Best'] = Fore.GREEN + trials.loc[i]['Best'] trials.at[i, 'Objective'] = "{}{}".format(trials.loc[i]['Objective'], Fore.RESET) - if '*' in trials.loc[i]['Best'] and highlight_best: - trials.at[i, 'Best'] = Style.BRIGHT + trials.loc[i]['Best'] - trials.at[i, 'Objective'] = "{}{}".format(trials.loc[i]['Objective'], - Style.RESET_ALL) + if 'Best' in trials.loc[i]['Best'] and highlight_best: + trials.at[i, 'Best'] = Style.BRIGHT + trials.loc[i]['Best'] + trials.at[i, 'Objective'] = "{}{}".format(trials.loc[i]['Objective'], + Style.RESET_ALL) trials['Epoch'] = trials['Epoch'].apply( lambda x: "{}/{}".format(x, total_epochs)) @@ -338,7 +341,8 @@ class Hyperopt: trials['Avg duration'] = trials['Avg duration'].apply( lambda x: '{:,.1f}m'.format(x) if not isna(x) else x) - print(tabulate(trials.to_dict(orient='list'), headers='keys', tablefmt='psql')) + print(tabulate(trials.to_dict(orient='list'), headers='keys', tablefmt='psql', + stralign="right")) def has_space(self, space: str) -> bool: """ From 6581ba56cab28f025c7444bd57e2e09beefb7ded Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 24 Feb 2020 20:41:45 +0100 Subject: [PATCH 027/202] Use markets.quote to validate --- freqtrade/pairlist/IPairList.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/pairlist/IPairList.py b/freqtrade/pairlist/IPairList.py index 1ad4da523..7d489ece7 100644 --- a/freqtrade/pairlist/IPairList.py +++ b/freqtrade/pairlist/IPairList.py @@ -99,7 +99,8 @@ class IPairList(ABC): logger.warning(f"Pair {pair} is not compatible with exchange " f"{self._exchange.name}. Removing it from whitelist..") continue - if not pair.endswith(self._config['stake_currency']): + + if markets[pair]['quote'] != self._config['stake_currency']: logger.warning(f"Pair {pair} is not compatible with your stake currency " f"{self._config['stake_currency']}. Removing it from whitelist..") continue From 3e4f663418236f84123eeb186fa0f642f84de89e Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 24 Feb 2020 21:22:58 +0100 Subject: [PATCH 028/202] Move pairlist validation to exchange (we need to use .quote) from markets --- freqtrade/configuration/config_validation.py | 12 ------ freqtrade/exchange/exchange.py | 8 +++- tests/exchange/test_exchange.py | 42 ++++++++++++++++++-- tests/test_configuration.py | 7 ---- 4 files changed, 45 insertions(+), 24 deletions(-) diff --git a/freqtrade/configuration/config_validation.py b/freqtrade/configuration/config_validation.py index 5183ad0b4..5ba7ff294 100644 --- a/freqtrade/configuration/config_validation.py +++ b/freqtrade/configuration/config_validation.py @@ -150,15 +150,3 @@ def _validate_whitelist(conf: Dict[str, Any]) -> None: if (pl.get('method') == 'StaticPairList' and not conf.get('exchange', {}).get('pair_whitelist')): raise OperationalException("StaticPairList requires pair_whitelist to be set.") - - if pl.get('method') == 'StaticPairList': - stake = conf['stake_currency'] - invalid_pairs = [] - for pair in conf['exchange'].get('pair_whitelist'): - if not pair.endswith(f'/{stake}'): - invalid_pairs.append(pair) - - if invalid_pairs: - raise OperationalException( - f"Stake-currency '{stake}' not compatible with pair-whitelist. " - f"Please remove the following pairs: {invalid_pairs}") diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index b3b347016..8023417a3 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -300,7 +300,7 @@ class Exchange: if not self.markets: logger.warning('Unable to validate pairs (assuming they are correct).') return - + invalid_pairs = [] for pair in pairs: # Note: ccxt has BaseCurrency/QuoteCurrency format for pairs # TODO: add a support for having coins in BTC/USDT format @@ -322,6 +322,12 @@ class Exchange: logger.warning(f"Pair {pair} is restricted for some users on this exchange." f"Please check if you are impacted by this restriction " f"on the exchange and eventually remove {pair} from your whitelist.") + if not self.markets[pair].get('quote') == self._config['stake_currency']: + invalid_pairs.append(pair) + if invalid_pairs: + raise OperationalException( + f"Stake-currency '{self._config['stake_currency']}' not compatible with " + f"pair-whitelist. Please remove the following pairs: {invalid_pairs}") def get_valid_pair_combination(self, curr_1: str, curr_2: str) -> str: """ diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 8b2e439c3..d1c105591 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -406,7 +406,10 @@ def test_get_quote_currencies(default_conf, mocker): def test_validate_pairs(default_conf, mocker): # test exchange.validate_pairs directly api_mock = MagicMock() type(api_mock).markets = PropertyMock(return_value={ - 'ETH/BTC': {}, 'LTC/BTC': {}, 'XRP/BTC': {}, 'NEO/BTC': {} + 'ETH/BTC': {'quote': 'BTC'}, + 'LTC/BTC': {'quote': 'BTC'}, + 'XRP/BTC': {'quote': 'BTC'}, + 'NEO/BTC': {'quote': 'BTC'}, }) id_mock = PropertyMock(return_value='test_exchange') type(api_mock).id = id_mock @@ -454,9 +457,9 @@ def test_validate_pairs_exception(default_conf, mocker, caplog): def test_validate_pairs_restricted(default_conf, mocker, caplog): api_mock = MagicMock() type(api_mock).markets = PropertyMock(return_value={ - 'ETH/BTC': {}, 'LTC/BTC': {}, - 'XRP/BTC': {'info': {'IsRestricted': True}}, - 'NEO/BTC': {'info': 'TestString'}, # info can also be a string ... + 'ETH/BTC': {'quote': 'BTC'}, 'LTC/BTC': {'quote': 'BTC'}, + 'XRP/BTC': {'quote': 'BTC', 'info': {'IsRestricted': True}}, + 'NEO/BTC': {'quote': 'BTC', 'info': 'TestString'}, # info can also be a string ... }) mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') @@ -469,6 +472,37 @@ def test_validate_pairs_restricted(default_conf, mocker, caplog): f"on the exchange and eventually remove XRP/BTC from your whitelist.", caplog) +def test_validate_pairs_stakecompatibility(default_conf, mocker, caplog): + api_mock = MagicMock() + type(api_mock).markets = PropertyMock(return_value={ + 'ETH/BTC': {'quote': 'BTC'}, 'LTC/BTC': {'quote': 'BTC'}, + 'XRP/BTC': {'quote': 'BTC'}, 'NEO/BTC': {'quote': 'BTC'}, + 'HELLO-WORLD': {'quote': 'BTC'}, + }) + mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) + mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') + mocker.patch('freqtrade.exchange.Exchange._load_async_markets') + mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') + + Exchange(default_conf) + + +def test_validate_pairs_stakecompatibility_fail(default_conf, mocker, caplog): + default_conf['exchange']['pair_whitelist'].append('HELLO-WORLD') + api_mock = MagicMock() + type(api_mock).markets = PropertyMock(return_value={ + 'ETH/BTC': {'quote': 'BTC'}, 'LTC/BTC': {'quote': 'BTC'}, + 'XRP/BTC': {'quote': 'BTC'}, 'NEO/BTC': {'quote': 'BTC'}, + 'HELLO-WORLD': {'quote': 'USDT'}, + }) + mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) + mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') + mocker.patch('freqtrade.exchange.Exchange._load_async_markets') + mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') + + with pytest.raises(OperationalException, match=r"Stake-currency 'BTC' not compatible with.*"): + Exchange(default_conf) + @pytest.mark.parametrize("timeframe", [ ('5m'), ("1m"), ("15m"), ("1h") ]) diff --git a/tests/test_configuration.py b/tests/test_configuration.py index d810305db..828db4d83 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -810,13 +810,6 @@ def test_validate_whitelist(default_conf): validate_config_consistency(conf) - conf = deepcopy(default_conf) - conf['stake_currency'] = 'USDT' - with pytest.raises(OperationalException, - match=r"Stake-currency 'USDT' not compatible with pair-whitelist.*"): - validate_config_consistency(conf) - - def test_load_config_test_comments() -> None: """ Load config with comments From 61037ab7b8f3f2467555c1a2a767580ea25f43a6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 24 Feb 2020 21:50:27 +0100 Subject: [PATCH 029/202] Implement get_pair_base_curr and get_pair_quote_curr --- freqtrade/exchange/exchange.py | 12 ++++++++++++ freqtrade/freqtradebot.py | 6 ++++-- tests/commands/test_commands.py | 31 ++++++++++++++++--------------- tests/conftest.py | 29 ++++++++++++++++++++++++++++- tests/exchange/test_exchange.py | 15 ++++++++------- tests/test_freqtradebot.py | 2 ++ 6 files changed, 70 insertions(+), 25 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 8023417a3..0e0d2dabe 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -228,6 +228,18 @@ class Exchange: markets = self.markets return sorted(set([x['quote'] for _, x in markets.items()])) + def get_pair_quote_currency(self, pair: str) -> str: + """ + Return a pair's quote currency + """ + return self.markets[pair].get('quote') + + def get_pair_base_currency(self, pair: str) -> str: + """ + Return a pair's quote currency + """ + return self.markets[pair].get('base') + def klines(self, pair_interval: Tuple[str, str], copy: bool = True) -> DataFrame: if pair_interval in self._klines: return self._klines[pair_interval].copy() if copy else self._klines[pair_interval] diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 00d5c369a..38583b5ad 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -1125,12 +1125,13 @@ class FreqtradeBot: if trade.fee_open == 0 or order['status'] == 'open': return order_amount + trade_base_currency = self.exchange.get_pair_base_currency(trade.pair) # use fee from order-dict if possible if ('fee' in order and order['fee'] is not None and (order['fee'].keys() >= {'currency', 'cost'})): if (order['fee']['currency'] is not None and order['fee']['cost'] is not None and - trade.pair.startswith(order['fee']['currency'])): + trade_base_currency == order['fee']['currency']): new_amount = order_amount - order['fee']['cost'] logger.info("Applying fee on amount for %s (from %s to %s) from Order", trade, order['amount'], new_amount) @@ -1145,6 +1146,7 @@ class FreqtradeBot: return order_amount amount = 0 fee_abs = 0 + trade_base_currency = self.exchange.get_pair_base_currency(trade.pair) for exectrade in trades: amount += exectrade['amount'] if ("fee" in exectrade and exectrade['fee'] is not None and @@ -1152,7 +1154,7 @@ class FreqtradeBot: # only applies if fee is in quote currency! if (exectrade['fee']['currency'] is not None and exectrade['fee']['cost'] is not None and - trade.pair.startswith(exectrade['fee']['currency'])): + trade_base_currency == exectrade['fee']['currency']): fee_abs += exectrade['fee']['cost'] if not isclose(amount, order_amount, abs_tol=constants.MATH_CLOSE_PREC): diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index a9fe0f637..fd8df4b56 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -217,8 +217,9 @@ def test_list_markets(mocker, markets, capsys): ] start_list_markets(get_args(args), False) captured = capsys.readouterr() - assert ("Exchange Bittrex has 9 active markets: " - "BLK/BTC, ETH/BTC, ETH/USDT, LTC/BTC, LTC/USD, NEO/BTC, TKN/BTC, XLTCUSDT, XRP/BTC.\n" + assert ("Exchange Bittrex has 10 active markets: " + "BLK/BTC, ETH/BTC, ETH/USDT, LTC/BTC, LTC/ETH, LTC/USD, NEO/BTC, " + "TKN/BTC, XLTCUSDT, XRP/BTC.\n" in captured.out) patch_exchange(mocker, api_mock=api_mock, id="binance") @@ -231,7 +232,7 @@ def test_list_markets(mocker, markets, capsys): pargs['config'] = None start_list_markets(pargs, False) captured = capsys.readouterr() - assert re.match("\nExchange Binance has 9 active markets:\n", + assert re.match("\nExchange Binance has 10 active markets:\n", captured.out) patch_exchange(mocker, api_mock=api_mock, id="bittrex") @@ -243,8 +244,8 @@ def test_list_markets(mocker, markets, capsys): ] start_list_markets(get_args(args), False) captured = capsys.readouterr() - assert ("Exchange Bittrex has 11 markets: " - "BLK/BTC, BTT/BTC, ETH/BTC, ETH/USDT, LTC/BTC, LTC/USD, LTC/USDT, NEO/BTC, " + assert ("Exchange Bittrex has 12 markets: " + "BLK/BTC, BTT/BTC, ETH/BTC, ETH/USDT, LTC/BTC, LTC/ETH, LTC/USD, LTC/USDT, NEO/BTC, " "TKN/BTC, XLTCUSDT, XRP/BTC.\n" in captured.out) @@ -256,8 +257,8 @@ def test_list_markets(mocker, markets, capsys): ] start_list_markets(get_args(args), True) captured = capsys.readouterr() - assert ("Exchange Bittrex has 8 active pairs: " - "BLK/BTC, ETH/BTC, ETH/USDT, LTC/BTC, LTC/USD, NEO/BTC, TKN/BTC, XRP/BTC.\n" + assert ("Exchange Bittrex has 9 active pairs: " + "BLK/BTC, ETH/BTC, ETH/USDT, LTC/BTC, LTC/ETH, LTC/USD, NEO/BTC, TKN/BTC, XRP/BTC.\n" in captured.out) # Test list-pairs subcommand with --all: all pairs @@ -268,8 +269,8 @@ def test_list_markets(mocker, markets, capsys): ] start_list_markets(get_args(args), True) captured = capsys.readouterr() - assert ("Exchange Bittrex has 10 pairs: " - "BLK/BTC, BTT/BTC, ETH/BTC, ETH/USDT, LTC/BTC, LTC/USD, LTC/USDT, NEO/BTC, " + assert ("Exchange Bittrex has 11 pairs: " + "BLK/BTC, BTT/BTC, ETH/BTC, ETH/USDT, LTC/BTC, LTC/ETH, LTC/USD, LTC/USDT, NEO/BTC, " "TKN/BTC, XRP/BTC.\n" in captured.out) @@ -282,8 +283,8 @@ def test_list_markets(mocker, markets, capsys): ] start_list_markets(get_args(args), False) captured = capsys.readouterr() - assert ("Exchange Bittrex has 5 active markets with ETH, LTC as base currencies: " - "ETH/BTC, ETH/USDT, LTC/BTC, LTC/USD, XLTCUSDT.\n" + assert ("Exchange Bittrex has 6 active markets with ETH, LTC as base currencies: " + "ETH/BTC, ETH/USDT, LTC/BTC, LTC/ETH, LTC/USD, XLTCUSDT.\n" in captured.out) # active markets, base=LTC @@ -295,8 +296,8 @@ def test_list_markets(mocker, markets, capsys): ] start_list_markets(get_args(args), False) captured = capsys.readouterr() - assert ("Exchange Bittrex has 3 active markets with LTC as base currency: " - "LTC/BTC, LTC/USD, XLTCUSDT.\n" + assert ("Exchange Bittrex has 4 active markets with LTC as base currency: " + "LTC/BTC, LTC/ETH, LTC/USD, XLTCUSDT.\n" in captured.out) # active markets, quote=USDT, USD @@ -384,7 +385,7 @@ def test_list_markets(mocker, markets, capsys): ] start_list_markets(get_args(args), False) captured = capsys.readouterr() - assert ("Exchange Bittrex has 9 active markets:\n" + assert ("Exchange Bittrex has 10 active markets:\n" in captured.out) # Test tabular output, no markets found @@ -407,7 +408,7 @@ def test_list_markets(mocker, markets, capsys): ] start_list_markets(get_args(args), False) captured = capsys.readouterr() - assert ('["BLK/BTC","ETH/BTC","ETH/USDT","LTC/BTC","LTC/USD","NEO/BTC",' + assert ('["BLK/BTC","ETH/BTC","ETH/USDT","LTC/BTC","LTC/ETH","LTC/USD","NEO/BTC",' '"TKN/BTC","XLTCUSDT","XRP/BTC"]' in captured.out) diff --git a/tests/conftest.py b/tests/conftest.py index acb730330..000f62868 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -575,7 +575,34 @@ def get_markets(): } }, 'info': {}, - } + }, + 'LTC/ETH': { + 'id': 'LTCETH', + 'symbol': 'LTC/ETH', + 'base': 'LTC', + 'quote': 'ETH', + 'active': True, + 'precision': { + 'base': 8, + 'quote': 8, + 'amount': 3, + 'price': 5 + }, + 'limits': { + 'amount': { + 'min': 0.001, + 'max': 10000000.0 + }, + 'price': { + 'min': 1e-05, + 'max': 1000.0 + }, + 'cost': { + 'min': 0.01, + 'max': None + } + }, + }, } diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index d1c105591..98edd0dbb 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -400,7 +400,7 @@ def test_validate_stake_currency_error(default_conf, mocker, caplog): def test_get_quote_currencies(default_conf, mocker): ex = get_patched_exchange(mocker, default_conf) - assert set(ex.get_quote_currencies()) == set(['USD', 'BTC', 'USDT']) + assert set(ex.get_quote_currencies()) == set(['USD', 'ETH', 'BTC', 'USDT']) def test_validate_pairs(default_conf, mocker): # test exchange.validate_pairs directly @@ -1862,6 +1862,7 @@ def test_get_valid_pair_combination(default_conf, mocker, markets): # 'ETH/BTC': 'active': True # 'ETH/USDT': 'active': True # 'LTC/BTC': 'active': False + # 'LTC/ETH': 'active': True # 'LTC/USD': 'active': True # 'LTC/USDT': 'active': True # 'NEO/BTC': 'active': False @@ -1870,26 +1871,26 @@ def test_get_valid_pair_combination(default_conf, mocker, markets): # 'XRP/BTC': 'active': False # all markets ([], [], False, False, - ['BLK/BTC', 'BTT/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/USD', + ['BLK/BTC', 'BTT/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/ETH', 'LTC/USD', 'LTC/USDT', 'NEO/BTC', 'TKN/BTC', 'XLTCUSDT', 'XRP/BTC']), # active markets ([], [], False, True, - ['BLK/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/USD', 'NEO/BTC', + ['BLK/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/ETH', 'LTC/USD', 'NEO/BTC', 'TKN/BTC', 'XLTCUSDT', 'XRP/BTC']), # all pairs ([], [], True, False, - ['BLK/BTC', 'BTT/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/USD', + ['BLK/BTC', 'BTT/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/ETH', 'LTC/USD', 'LTC/USDT', 'NEO/BTC', 'TKN/BTC', 'XRP/BTC']), # active pairs ([], [], True, True, - ['BLK/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/USD', 'NEO/BTC', + ['BLK/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/ETH', 'LTC/USD', 'NEO/BTC', 'TKN/BTC', 'XRP/BTC']), # all markets, base=ETH, LTC (['ETH', 'LTC'], [], False, False, - ['ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/USD', 'LTC/USDT', 'XLTCUSDT']), + ['ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/ETH', 'LTC/USD', 'LTC/USDT', 'XLTCUSDT']), # all markets, base=LTC (['LTC'], [], False, False, - ['LTC/BTC', 'LTC/USD', 'LTC/USDT', 'XLTCUSDT']), + ['LTC/BTC', 'LTC/ETH', 'LTC/USD', 'LTC/USDT', 'XLTCUSDT']), # all markets, quote=USDT ([], ['USDT'], False, False, ['ETH/USDT', 'LTC/USDT', 'XLTCUSDT']), diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 20db46fac..9a7816d35 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -2192,6 +2192,7 @@ def test_handle_timedout_limit_buy(mocker, default_conf, limit_buy_order) -> Non Trade.session = MagicMock() trade = MagicMock() + trade.pair = 'LTC/ETH' limit_buy_order['remaining'] = limit_buy_order['amount'] assert freqtrade.handle_timedout_limit_buy(trade, limit_buy_order) assert cancel_order_mock.call_count == 1 @@ -2215,6 +2216,7 @@ def test_handle_timedout_limit_buy_corder_empty(mocker, default_conf, limit_buy_ Trade.session = MagicMock() trade = MagicMock() + trade.pair = 'LTC/ETH' limit_buy_order['remaining'] = limit_buy_order['amount'] assert freqtrade.handle_timedout_limit_buy(trade, limit_buy_order) assert cancel_order_mock.call_count == 1 From cd7efde6c0618c3a40d44432dece4592510e5c87 Mon Sep 17 00:00:00 2001 From: Fredrik81 Date: Mon, 24 Feb 2020 22:06:21 +0100 Subject: [PATCH 030/202] Fixed coloring so it's only targeting the values not the table borders --- freqtrade/optimize/hyperopt.py | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 7ff5c3500..9c18d6803 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -313,22 +313,12 @@ class Hyperopt: 'loss', 'is_initial_point', 'is_best']] trials.columns = ['Best', 'Epoch', 'Trades', 'Avg profit', 'Total profit', 'Profit', 'Avg duration', 'Objective', 'is_initial_point', 'is_best'] - + trials['is_profit'] = False trials.loc[trials['is_initial_point'], 'Best'] = '*' trials.loc[trials['is_best'], 'Best'] = 'Best' trials['Objective'] = trials['Objective'].astype(str) - trials = trials.drop(columns=['is_initial_point', 'is_best']) - - if print_colorized: - for i in range(len(trials)): - if trials.loc[i]['Total profit'] > 0: - trials.at[i, 'Best'] = Fore.GREEN + trials.loc[i]['Best'] - trials.at[i, 'Objective'] = "{}{}".format(trials.loc[i]['Objective'], - Fore.RESET) - if 'Best' in trials.loc[i]['Best'] and highlight_best: - trials.at[i, 'Best'] = Style.BRIGHT + trials.loc[i]['Best'] - trials.at[i, 'Objective'] = "{}{}".format(trials.loc[i]['Objective'], - Style.RESET_ALL) + trials.loc[trials['Total profit'] > 0, 'is_profit'] = True + trials['Trades'] = trials['Trades'].astype(str) trials['Epoch'] = trials['Epoch'].apply( lambda x: "{}/{}".format(x, total_epochs)) @@ -340,9 +330,21 @@ class Hyperopt: lambda x: '{: 11.8f} '.format(x) + config['stake_currency'] if not isna(x) else x) trials['Avg duration'] = trials['Avg duration'].apply( lambda x: '{:,.1f}m'.format(x) if not isna(x) else x) + if print_colorized: + for i in range(len(trials)): + if trials.loc[i]['is_profit']: + for z in range(len(trials.loc[i])-3): + trials.iat[i, z] = "{}{}{}".format(Fore.GREEN, + str(trials.loc[i][z]), Fore.RESET) + if trials.loc[i]['is_best'] and highlight_best: + for z in range(len(trials.loc[i])-3): + trials.iat[i, z] = "{}{}{}".format(Style.BRIGHT, + str(trials.loc[i][z]), Style.RESET_ALL) + + trials = trials.drop(columns=['is_initial_point', 'is_best', 'is_profit']) print(tabulate(trials.to_dict(orient='list'), headers='keys', tablefmt='psql', - stralign="right")) + stralign="right")) def has_space(self, space: str) -> bool: """ From e9448dc5e248f702b80ddd9be62faefd6e1526d1 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 25 Feb 2020 07:01:23 +0100 Subject: [PATCH 031/202] Add tsts for quote and base currency --- tests/exchange/test_exchange.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 98edd0dbb..def4e6ab6 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -403,6 +403,28 @@ def test_get_quote_currencies(default_conf, mocker): assert set(ex.get_quote_currencies()) == set(['USD', 'ETH', 'BTC', 'USDT']) +@pytest.mark.parametrize('pair,expected', [ + ('XRP/BTC', 'BTC'), + ('LTC/USD', 'USD'), + ('ETH/USDT', 'USDT'), + ('XLTCUSDT', 'USDT'), +]) +def test_get_pair_quote_currency(default_conf, mocker, pair, expected): + ex = get_patched_exchange(mocker, default_conf) + assert ex.get_pair_quote_currency(pair) == expected + + +@pytest.mark.parametrize('pair,expected', [ + ('XRP/BTC', 'XRP'), + ('LTC/USD', 'LTC'), + ('ETH/USDT', 'ETH'), + ('XLTCUSDT', 'LTC'), +]) +def test_get_pair_base_currency(default_conf, mocker, pair, expected): + ex = get_patched_exchange(mocker, default_conf) + assert ex.get_pair_base_currency(pair) == expected + + def test_validate_pairs(default_conf, mocker): # test exchange.validate_pairs directly api_mock = MagicMock() type(api_mock).markets = PropertyMock(return_value={ From e8eaa8920ea397a9403ece95d27c3a647ed428d8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 25 Feb 2020 07:01:31 +0100 Subject: [PATCH 032/202] Use get_base_currency instead of splitting by / --- freqtrade/freqtradebot.py | 3 +-- freqtrade/wallets.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 38583b5ad..424a6a220 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -938,8 +938,7 @@ class FreqtradeBot: """ # Update wallets to ensure amounts tied up in a stoploss is now free! self.wallets.update() - - wallet_amount = self.wallets.get_free(pair.split('/')[0]) + wallet_amount = self.wallets.get_free(self.exchange.get_pair_base_currency(pair)) logger.debug(f"{pair} - Wallet: {wallet_amount} - Trade-amount: {amount}") if wallet_amount >= amount: return amount diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py index dd5e34fe6..b913155bc 100644 --- a/freqtrade/wallets.py +++ b/freqtrade/wallets.py @@ -74,7 +74,7 @@ class Wallets: ) for trade in open_trades: - curr = trade.pair.split('/')[0] + curr = self._exchange.get_pair_base_currency(trade.pair) _wallets[curr] = Wallet( curr, trade.amount, From d34515a5de5b08e21cfc2e6854d4037758fc1f11 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 25 Feb 2020 07:03:11 +0100 Subject: [PATCH 033/202] Remove constraint to have pairs in base/quote format --- freqtrade/constants.py | 2 -- tests/exchange/test_exchange.py | 1 + tests/test_configuration.py | 8 +------- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 105cd6b53..1504d1f1c 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -251,7 +251,6 @@ CONF_SCHEMA = { 'type': 'array', 'items': { 'type': 'string', - 'pattern': '^[0-9A-Z]+/[0-9A-Z]+$' }, 'uniqueItems': True }, @@ -259,7 +258,6 @@ CONF_SCHEMA = { 'type': 'array', 'items': { 'type': 'string', - 'pattern': '^[0-9A-Z]+/[0-9A-Z]+$' }, 'uniqueItems': True }, diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index def4e6ab6..ca2bedb01 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -525,6 +525,7 @@ def test_validate_pairs_stakecompatibility_fail(default_conf, mocker, caplog): with pytest.raises(OperationalException, match=r"Stake-currency 'BTC' not compatible with.*"): Exchange(default_conf) + @pytest.mark.parametrize("timeframe", [ ('5m'), ("1m"), ("15m"), ("1h") ]) diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 828db4d83..a58e88ea0 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -34,13 +34,6 @@ def all_conf(): return conf -def test_load_config_invalid_pair(default_conf) -> None: - default_conf['exchange']['pair_whitelist'].append('ETH-BTC') - - with pytest.raises(ValidationError, match=r'.*does not match.*'): - validate_config_schema(default_conf) - - def test_load_config_missing_attributes(default_conf) -> None: conf = deepcopy(default_conf) conf.pop('exchange') @@ -810,6 +803,7 @@ def test_validate_whitelist(default_conf): validate_config_consistency(conf) + def test_load_config_test_comments() -> None: """ Load config with comments From 31ac4598ba966b60be897ef22884442fa63eeb6f Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 25 Feb 2020 07:16:37 +0100 Subject: [PATCH 034/202] Fix last occurances of pair splitting --- freqtrade/exchange/exchange.py | 4 ++-- freqtrade/pairlist/VolumePairList.py | 4 ++-- freqtrade/rpc/rpc.py | 4 ++-- tests/rpc/test_rpc.py | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 0e0d2dabe..6964986b0 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -232,13 +232,13 @@ class Exchange: """ Return a pair's quote currency """ - return self.markets[pair].get('quote') + return self.markets.get(pair, {}).get('quote') def get_pair_base_currency(self, pair: str) -> str: """ Return a pair's quote currency """ - return self.markets[pair].get('base') + return self.markets.get(pair, {}).get('base') def klines(self, pair_interval: Tuple[str, str], copy: bool = True) -> DataFrame: if pair_interval in self._klines: diff --git a/freqtrade/pairlist/VolumePairList.py b/freqtrade/pairlist/VolumePairList.py index e50dafb63..e1cdb4a43 100644 --- a/freqtrade/pairlist/VolumePairList.py +++ b/freqtrade/pairlist/VolumePairList.py @@ -91,9 +91,9 @@ class VolumePairList(IPairList): if self._pairlist_pos == 0: # If VolumePairList is the first in the list, use fresh pairlist - # check length so that we make sure that '/' is actually in the string + # check base currency equals to stake currency. filtered_tickers = [v for k, v in tickers.items() - if (len(k.split('/')) == 2 and k.split('/')[1] == base_currency + if (self._exchange.get_pair_quote_currency(k) == base_currency and v[key] is not None)] else: # If other pairlist is in front, use the incomming pairlist. diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 3411318bb..d680d36b1 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -462,7 +462,7 @@ class RPC: # Check pair is in stake currency stake_currency = self._freqtrade.config.get('stake_currency') - if not pair.endswith(stake_currency): + if not self._freqtrade.exchange.get_pair_quote_currency(pair) == stake_currency: raise RPCException( f'Wrong pair selected. Please pairs with stake {stake_currency} pairs only') # check if valid pair @@ -517,7 +517,7 @@ class RPC: if add: stake_currency = self._freqtrade.config.get('stake_currency') for pair in add: - if (pair.endswith(stake_currency) + if (self._freqtrade.exchange.get_pair_quote_currency(pair) == stake_currency and pair not in self._freqtrade.pairlists.blacklist): self._freqtrade.pairlists.blacklist.append(pair) diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index a35bfa0d6..3c99e6b92 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -687,7 +687,7 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, limit_buy_order) -> None # Test buy pair not with stakes with pytest.raises(RPCException, match=r'Wrong pair selected. Please pairs with stake.*'): - rpc._rpc_forcebuy('XRP/ETH', 0.0001) + rpc._rpc_forcebuy('LTC/ETH', 0.0001) pair = 'XRP/BTC' # Test not buying From cfc22577bed042947d40cfa15676c1456aa6f7d4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 25 Feb 2020 16:52:01 +0100 Subject: [PATCH 035/202] Add timeframe_to_minutes to ROI documentation --- docs/strategy-customization.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/strategy-customization.md b/docs/strategy-customization.md index 07833da34..0dfeb3978 100644 --- a/docs/strategy-customization.md +++ b/docs/strategy-customization.md @@ -249,6 +249,22 @@ minimal_roi = { While technically not completely disabled, this would sell once the trade reaches 10000% Profit. +To use times based on candles, the following snippet can be handy. +This will allow you to change the ticket_interval, and ROI will be set as candles (e.g. after 3 candles ...) + +``` python +from freqtrade.exchange import timeframe_to_minutes + +class AwesomeStrategy(IStrategy): + + ticker_interval = '1d' + ticker_interval_mins = timeframe_to_minutes(ticker_interval) + minimal_roi = { + (ticker_interval_mins * 3): 0.02, # After 3 candles + (ticker_interval_mins * 6): 0.01, # After 6 candles + } +``` + ### Stoploss Setting a stoploss is highly recommended to protect your capital from strong moves against you. From 7d7318a3ea5af47dada77e6595142e8c4bba8237 Mon Sep 17 00:00:00 2001 From: gaugau3000 Date: Tue, 25 Feb 2020 19:41:20 +0000 Subject: [PATCH 036/202] fix_wrong_order_type --- docs/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index 0b9519688..6f139bebe 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -340,7 +340,7 @@ This is most of the time the default time in force. It means the order will rema on exchange till it is canceled by user. It can be fully or partially fulfilled. If partially fulfilled, the remaining will stay on the exchange till cancelled. -**FOK (Full Or Kill):** +**FOK (Fill Or Kill):** It means if the order is not executed immediately AND fully then it is canceled by the exchange. From 76c449c0c232032d128a854764356cebfe504fcb Mon Sep 17 00:00:00 2001 From: gaugau3000 Date: Tue, 25 Feb 2020 19:45:23 +0000 Subject: [PATCH 037/202] volume_pair_list_extra_doc_infos --- docs/configuration.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/configuration.md b/docs/configuration.md index 6f139bebe..6e8813211 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -532,6 +532,8 @@ It uses configuration from `exchange.pair_whitelist` and `exchange.pair_blacklis `refresh_period` allows setting the period (in seconds), at which the pairlist will be refreshed. Defaults to 1800s (30 minutes). +`VolumePairList` is based on the volume of the last 24 hours. + ```json "pairlists": [{ "method": "VolumePairList", From ce2e039e5fb9eba15864a1a3df45be17120127d7 Mon Sep 17 00:00:00 2001 From: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> Date: Wed, 26 Feb 2020 01:58:32 +0300 Subject: [PATCH 038/202] Update docs/configuration.md --- docs/configuration.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index 6e8813211..5d1820bdb 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -532,7 +532,11 @@ It uses configuration from `exchange.pair_whitelist` and `exchange.pair_blacklis `refresh_period` allows setting the period (in seconds), at which the pairlist will be refreshed. Defaults to 1800s (30 minutes). -`VolumePairList` is based on the volume of the last 24 hours. +`VolumePairList` is based on the ticker data, as reported by the ccxt library: + +* The `bidVolume` is the volume (amount) of current best bid in the orderbook. +* The `askVolume` is the volume (amount) of current best ask in the orderbook. +* The `quoteVolume` is the amount of quote (stake) currency traded (bought or sold) in last 24 hours. ```json "pairlists": [{ From df49b98c2578a9d45c0a052f643d00e6fbba2bdd Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 26 Feb 2020 06:40:13 +0100 Subject: [PATCH 039/202] Implement wording-changes Co-Authored-By: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> --- docs/strategy-customization.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/strategy-customization.md b/docs/strategy-customization.md index 0dfeb3978..acd985287 100644 --- a/docs/strategy-customization.md +++ b/docs/strategy-customization.md @@ -249,8 +249,8 @@ minimal_roi = { While technically not completely disabled, this would sell once the trade reaches 10000% Profit. -To use times based on candles, the following snippet can be handy. -This will allow you to change the ticket_interval, and ROI will be set as candles (e.g. after 3 candles ...) +To use times based on candle duration (ticker_interval or timeframe), the following snippet can be handy. +This will allow you to change the ticket_interval for the strategy, and ROI times will still be set as candles (e.g. after 3 candles ...) ``` python from freqtrade.exchange import timeframe_to_minutes From af4469f073501150c4aaa520fcd614ea18177fad Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 26 Feb 2020 06:43:15 +0100 Subject: [PATCH 040/202] Convert to str to avoid errors --- docs/strategy-customization.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/strategy-customization.md b/docs/strategy-customization.md index acd985287..fd40a971e 100644 --- a/docs/strategy-customization.md +++ b/docs/strategy-customization.md @@ -260,8 +260,8 @@ class AwesomeStrategy(IStrategy): ticker_interval = '1d' ticker_interval_mins = timeframe_to_minutes(ticker_interval) minimal_roi = { - (ticker_interval_mins * 3): 0.02, # After 3 candles - (ticker_interval_mins * 6): 0.01, # After 6 candles + str(ticker_interval_mins * 3)): 0.02, # After 3 candles + str(ticker_interval_mins * 6)): 0.01, # After 6 candles } ``` From 1e869b86f279f71c42d7f2bb6a34a2eb4ed60b47 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 26 Feb 2020 06:54:04 +0100 Subject: [PATCH 041/202] Update checkout aciton to v2 https://github.com/actions/checkout/issues/23 suggests that it's fixed in v2. --- .github/workflows/ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cc8906af5..dc3d324a1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ jobs: python-version: [3.7, 3.8] steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - name: Set up Python uses: actions/setup-python@v1 @@ -118,7 +118,7 @@ jobs: python-version: [3.7] steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - name: Set up Python uses: actions/setup-python@v1 @@ -175,7 +175,7 @@ jobs: docs_check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - name: Documentation syntax run: | @@ -195,7 +195,7 @@ jobs: runs-on: ubuntu-18.04 if: (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'release') && github.repository == 'freqtrade/freqtrade' steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - name: Set up Python uses: actions/setup-python@v1 From 1021ffa1c3051d17482a00e79eef5a2b211cd103 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 26 Feb 2020 07:00:08 +0100 Subject: [PATCH 042/202] Apply suggestions from code review Add suggested changes to comments Co-Authored-By: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> --- freqtrade/pairlist/VolumePairList.py | 2 +- freqtrade/rpc/rpc.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/pairlist/VolumePairList.py b/freqtrade/pairlist/VolumePairList.py index e1cdb4a43..d067d5e8a 100644 --- a/freqtrade/pairlist/VolumePairList.py +++ b/freqtrade/pairlist/VolumePairList.py @@ -91,7 +91,7 @@ class VolumePairList(IPairList): if self._pairlist_pos == 0: # If VolumePairList is the first in the list, use fresh pairlist - # check base currency equals to stake currency. + # Check if pair quote currency equals to the stake currency. filtered_tickers = [v for k, v in tickers.items() if (self._exchange.get_pair_quote_currency(k) == base_currency and v[key] is not None)] diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index d680d36b1..1e4eaa3e0 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -460,7 +460,7 @@ class RPC: if self._freqtrade.state != State.RUNNING: raise RPCException('trader is not running') - # Check pair is in stake currency + # Check if pair quote currency equals to the stake currency. stake_currency = self._freqtrade.config.get('stake_currency') if not self._freqtrade.exchange.get_pair_quote_currency(pair) == stake_currency: raise RPCException( From 4e218be51db55e16bccc5513be244b98467ccb9f Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 26 Feb 2020 07:08:09 +0100 Subject: [PATCH 043/202] Don't use markets[pair]['quote'] --- freqtrade/exchange/exchange.py | 2 +- freqtrade/freqtradebot.py | 4 ++-- freqtrade/pairlist/IPairList.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 6964986b0..cc0ecc6cd 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -334,7 +334,7 @@ class Exchange: logger.warning(f"Pair {pair} is restricted for some users on this exchange." f"Please check if you are impacted by this restriction " f"on the exchange and eventually remove {pair} from your whitelist.") - if not self.markets[pair].get('quote') == self._config['stake_currency']: + if not self.get_pair_quote_currency(pair) == self._config['stake_currency']: invalid_pairs.append(pair) if invalid_pairs: raise OperationalException( diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 424a6a220..c3b642095 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -938,7 +938,8 @@ class FreqtradeBot: """ # Update wallets to ensure amounts tied up in a stoploss is now free! self.wallets.update() - wallet_amount = self.wallets.get_free(self.exchange.get_pair_base_currency(pair)) + trade_base_currency = self.exchange.get_pair_base_currency(pair) + wallet_amount = self.wallets.get_free(trade_base_currency) logger.debug(f"{pair} - Wallet: {wallet_amount} - Trade-amount: {amount}") if wallet_amount >= amount: return amount @@ -1145,7 +1146,6 @@ class FreqtradeBot: return order_amount amount = 0 fee_abs = 0 - trade_base_currency = self.exchange.get_pair_base_currency(trade.pair) for exectrade in trades: amount += exectrade['amount'] if ("fee" in exectrade and exectrade['fee'] is not None and diff --git a/freqtrade/pairlist/IPairList.py b/freqtrade/pairlist/IPairList.py index 7d489ece7..d45a329dd 100644 --- a/freqtrade/pairlist/IPairList.py +++ b/freqtrade/pairlist/IPairList.py @@ -100,7 +100,7 @@ class IPairList(ABC): f"{self._exchange.name}. Removing it from whitelist..") continue - if markets[pair]['quote'] != self._config['stake_currency']: + if self._exchange.get_pair_quote_currency(pair) != self._config['stake_currency']: logger.warning(f"Pair {pair} is not compatible with your stake currency " f"{self._config['stake_currency']}. Removing it from whitelist..") continue From f38accb77b05accfbf328e62c5b417ed7613d527 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 26 Feb 2020 07:09:54 +0100 Subject: [PATCH 044/202] Return empty string if no quote / base currency can be found --- freqtrade/exchange/exchange.py | 4 ++-- tests/exchange/test_exchange.py | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index cc0ecc6cd..21627679f 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -232,13 +232,13 @@ class Exchange: """ Return a pair's quote currency """ - return self.markets.get(pair, {}).get('quote') + return self.markets.get(pair, {}).get('quote', '') def get_pair_base_currency(self, pair: str) -> str: """ Return a pair's quote currency """ - return self.markets.get(pair, {}).get('base') + return self.markets.get(pair, {}).get('base', '') def klines(self, pair_interval: Tuple[str, str], copy: bool = True) -> DataFrame: if pair_interval in self._klines: diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index ca2bedb01..3a653edb6 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -408,6 +408,7 @@ def test_get_quote_currencies(default_conf, mocker): ('LTC/USD', 'USD'), ('ETH/USDT', 'USDT'), ('XLTCUSDT', 'USDT'), + ('XRP/NOCURRENCY', ''), ]) def test_get_pair_quote_currency(default_conf, mocker, pair, expected): ex = get_patched_exchange(mocker, default_conf) @@ -419,6 +420,7 @@ def test_get_pair_quote_currency(default_conf, mocker, pair, expected): ('LTC/USD', 'LTC'), ('ETH/USDT', 'ETH'), ('XLTCUSDT', 'LTC'), + ('XRP/NOCURRENCY', ''), ]) def test_get_pair_base_currency(default_conf, mocker, pair, expected): ex = get_patched_exchange(mocker, default_conf) From f91d7beaa16edea12c6837489ae2f6d55e376a87 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 23 Feb 2020 13:51:16 +0100 Subject: [PATCH 045/202] Fix constants wrong parenteses --- freqtrade/constants.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 1504d1f1c..743070196 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -15,6 +15,7 @@ UNLIMITED_STAKE_AMOUNT = 'unlimited' DEFAULT_AMOUNT_RESERVE_PERCENT = 0.05 REQUIRED_ORDERTIF = ['buy', 'sell'] REQUIRED_ORDERTYPES = ['buy', 'sell', 'stoploss', 'stoploss_on_exchange'] +ORDERBOOK_SIDES = ['ask', 'bid'] ORDERTYPE_POSSIBILITIES = ['limit', 'market'] ORDERTIF_POSSIBILITIES = ['gtc', 'fok', 'ioc'] AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList', @@ -113,15 +114,15 @@ CONF_SCHEMA = { 'minimum': 0, 'maximum': 1, 'exclusiveMaximum': False, - 'use_order_book': {'type': 'boolean'}, - 'order_book_top': {'type': 'integer', 'maximum': 20, 'minimum': 1}, - 'check_depth_of_market': { - 'type': 'object', - 'properties': { - 'enabled': {'type': 'boolean'}, - 'bids_to_ask_delta': {'type': 'number', 'minimum': 0}, - } - }, + }, + 'use_order_book': {'type': 'boolean'}, + 'order_book_top': {'type': 'integer', 'maximum': 20, 'minimum': 1}, + 'check_depth_of_market': { + 'type': 'object', + 'properties': { + 'enabled': {'type': 'boolean'}, + 'bids_to_ask_delta': {'type': 'number', 'minimum': 0}, + } }, }, 'required': ['ask_last_balance'] From de48a697b0a5abdee5b012f2b5b919d0fe06cb9a Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 23 Feb 2020 14:06:42 +0100 Subject: [PATCH 046/202] Use price_side for get_sell_rate --- freqtrade/constants.py | 3 +++ freqtrade/freqtradebot.py | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 743070196..ac1a8a6a9 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -115,6 +115,7 @@ CONF_SCHEMA = { 'maximum': 1, 'exclusiveMaximum': False, }, + 'price_side': {'type': 'string', 'enum': ORDERBOOK_SIDES, 'default': 'bid'}, 'use_order_book': {'type': 'boolean'}, 'order_book_top': {'type': 'integer', 'maximum': 20, 'minimum': 1}, 'check_depth_of_market': { @@ -130,6 +131,7 @@ CONF_SCHEMA = { 'ask_strategy': { 'type': 'object', 'properties': { + 'price_side': {'type': 'string', 'enum': ORDERBOOK_SIDES, 'default': 'ask'}, 'use_order_book': {'type': 'boolean'}, 'order_book_min': {'type': 'integer', 'minimum': 1}, 'order_book_max': {'type': 'integer', 'minimum': 1, 'maximum': 50}, @@ -300,6 +302,7 @@ SCHEMA_TRADE_REQUIRED = [ 'last_stake_amount_min_ratio', 'dry_run', 'dry_run_wallet', + 'ask_strategy', 'bid_strategy', 'unfilledtimeout', 'stoploss', diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index dffec940c..5e8f1cc98 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -639,10 +639,10 @@ class FreqtradeBot: logger.debug('Using order book to get sell rate') order_book = self.exchange.get_order_book(pair, 1) - rate = order_book['bids'][0][0] + rate = order_book[f"{config_ask_strategy['price_side']}s"][0][0] else: - rate = self.exchange.fetch_ticker(pair)['bid'] + rate = self.exchange.fetch_ticker(pair)[config_ask_strategy['price_side']] self._sell_rate_cache[pair] = rate return rate From 5f712320380824f2140b82e5d6a328ff90f53beb Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 23 Feb 2020 14:08:16 +0100 Subject: [PATCH 047/202] Refactor get_buy_rate to use rate variable --- freqtrade/freqtradebot.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 5e8f1cc98..aaa66fe81 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -242,11 +242,10 @@ class FreqtradeBot: logger.info(f"Using cached buy rate for {pair}.") return rate - config_bid_strategy = self.config.get('bid_strategy', {}) - if 'use_order_book' in config_bid_strategy and\ - config_bid_strategy.get('use_order_book', False): + bid_strategy = self.config.get('bid_strategy', {}) + if 'use_order_book' in bid_strategy and bid_strategy.get('use_order_book', False): logger.info('Getting price from order book') - order_book_top = config_bid_strategy.get('order_book_top', 1) + order_book_top = bid_strategy.get('order_book_top', 1) order_book = self.exchange.get_order_book(pair, order_book_top) logger.debug('order_book %s', order_book) # top 1 = index 0 @@ -256,11 +255,12 @@ class FreqtradeBot: else: logger.info('Using Last Ask / Last Price') ticker = self.exchange.fetch_ticker(pair) - if ticker['ask'] < ticker['last']: - ticker_rate = ticker['ask'] + rate = ticker['ask'] + if rate < ticker['last']: + ticker_rate = rate else: balance = self.config['bid_strategy']['ask_last_balance'] - ticker_rate = ticker['ask'] + balance * (ticker['last'] - ticker['ask']) + ticker_rate = rate + balance * (ticker['last'] - rate) used_rate = ticker_rate self._buy_rate_cache[pair] = used_rate From e4b29491888c05444ec47599238926597ecac83a Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 23 Feb 2020 14:13:43 +0100 Subject: [PATCH 048/202] Change buy_rate calculation to use price_side --- freqtrade/freqtradebot.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index aaa66fe81..2c9a960a4 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -244,18 +244,20 @@ class FreqtradeBot: bid_strategy = self.config.get('bid_strategy', {}) if 'use_order_book' in bid_strategy and bid_strategy.get('use_order_book', False): - logger.info('Getting price from order book') + logger.info( + f"Getting price from order book {bid_strategy['price_side'].capitalize()} side." + ) order_book_top = bid_strategy.get('order_book_top', 1) order_book = self.exchange.get_order_book(pair, order_book_top) logger.debug('order_book %s', order_book) # top 1 = index 0 - order_book_rate = order_book['bids'][order_book_top - 1][0] - logger.info('...top %s order book buy rate %0.8f', order_book_top, order_book_rate) + order_book_rate = order_book[f"{bid_strategy['price_side']}s"][order_book_top - 1][0] + logger.info(f'...top {order_book_top} order book buy rate {order_book_rate:.8f}') used_rate = order_book_rate else: - logger.info('Using Last Ask / Last Price') + logger.info(f"Using Last {bid_strategy['price_side'].capitalize()} / Last Price") ticker = self.exchange.fetch_ticker(pair) - rate = ticker['ask'] + rate = ticker[bid_strategy['price_side']] if rate < ticker['last']: ticker_rate = rate else: From e7b9891335ba13abdcf1445b7cc354db8a51792e Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 23 Feb 2020 14:27:03 +0100 Subject: [PATCH 049/202] Adapt rpc tests to corrected price side --- tests/rpc/test_rpc.py | 24 +++++++++++----------- tests/rpc/test_rpc_apiserver.py | 8 ++++---- tests/rpc/test_rpc_telegram.py | 36 ++++++++++++++++----------------- 3 files changed, 34 insertions(+), 34 deletions(-) diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 93b6f6058..6319ab9e6 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -51,13 +51,13 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'open_date_hum': ANY, 'close_date': None, 'close_date_hum': None, - 'open_rate': 1.099e-05, + 'open_rate': 1.098e-05, 'close_rate': None, - 'current_rate': 1.098e-05, - 'amount': 90.99181074, + 'current_rate': 1.099e-05, + 'amount': 91.07468124, 'stake_amount': 0.001, 'close_profit': None, - 'current_profit': -0.59, + 'current_profit': -0.41, 'stop_loss': 0.0, 'initial_stop_loss': 0.0, 'initial_stop_loss_pct': None, @@ -78,10 +78,10 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'open_date_hum': ANY, 'close_date': None, 'close_date_hum': None, - 'open_rate': 1.099e-05, + 'open_rate': 1.098e-05, 'close_rate': None, 'current_rate': ANY, - 'amount': 90.99181074, + 'amount': 91.07468124, 'stake_amount': 0.001, 'close_profit': None, 'current_profit': ANY, @@ -121,7 +121,7 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None: assert "Pair" in headers assert 'instantly' == result[0][2] assert 'ETH/BTC' in result[0][1] - assert '-0.59%' == result[0][3] + assert '-0.41%' == result[0][3] # Test with fiatconvert rpc._fiat_converter = CryptoToFiatConverter() @@ -130,7 +130,7 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None: assert "Pair" in headers assert 'instantly' == result[0][2] assert 'ETH/BTC' in result[0][1] - assert '-0.59% (-0.09)' == result[0][3] + assert '-0.41% (-0.06)' == result[0][3] mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_sell_rate', MagicMock(side_effect=DependencyException(f"Pair 'ETH/BTC' not available"))) @@ -245,9 +245,9 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee, assert prec_satoshi(stats['profit_closed_coin'], 6.217e-05) assert prec_satoshi(stats['profit_closed_percent'], 6.2) assert prec_satoshi(stats['profit_closed_fiat'], 0.93255) - assert prec_satoshi(stats['profit_all_coin'], 5.632e-05) - assert prec_satoshi(stats['profit_all_percent'], 2.81) - assert prec_satoshi(stats['profit_all_fiat'], 0.8448) + assert prec_satoshi(stats['profit_all_coin'], 5.802e-05) + assert prec_satoshi(stats['profit_all_percent'], 2.89) + assert prec_satoshi(stats['profit_all_fiat'], 0.8703) assert stats['trade_count'] == 2 assert stats['first_trade_date'] == 'just now' assert stats['latest_trade_date'] == 'just now' @@ -668,7 +668,7 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, limit_buy_order) -> None trade = rpc._rpc_forcebuy(pair, None) assert isinstance(trade, Trade) assert trade.pair == pair - assert trade.open_rate == ticker()['ask'] + assert trade.open_rate == ticker()['bid'] # Test buy duplicate with pytest.raises(RPCException, match=r'position for ETH/BTC already open - id: 1'): diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 25c971bf7..e0abd886d 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -426,20 +426,20 @@ def test_api_status(botclient, mocker, ticker, fee, markets): rc = client_get(client, f"{BASE_URI}/status") assert_response(rc) assert len(rc.json) == 1 - assert rc.json == [{'amount': 90.99181074, + assert rc.json == [{'amount': 91.07468124, 'base_currency': 'BTC', 'close_date': None, 'close_date_hum': None, 'close_profit': None, 'close_rate': None, - 'current_profit': -0.59, - 'current_rate': 1.098e-05, + 'current_profit': -0.41, + 'current_rate': 1.099e-05, 'initial_stop_loss': 0.0, 'initial_stop_loss_pct': None, 'open_date': ANY, 'open_date_hum': 'just now', 'open_order': '(limit buy rem=0.00000000)', - 'open_rate': 1.099e-05, + 'open_rate': 1.098e-05, 'pair': 'ETH/BTC', 'stake_amount': 0.001, 'stop_loss': 0.0, diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index a8b8e0c5a..fd3e4039a 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -720,13 +720,13 @@ def test_forcesell_handle(default_conf, update, ticker, fee, 'exchange': 'Bittrex', 'pair': 'ETH/BTC', 'gain': 'profit', - 'limit': 1.172e-05, - 'amount': 90.99181073703367, + 'limit': 1.173e-05, + 'amount': 91.07468123861567, 'order_type': 'limit', - 'open_rate': 1.099e-05, - 'current_rate': 1.172e-05, - 'profit_amount': 6.126e-05, - 'profit_percent': 0.0611052, + 'open_rate': 1.098e-05, + 'current_rate': 1.173e-05, + 'profit_amount': 6.314e-05, + 'profit_percent': 0.0629778, 'stake_currency': 'BTC', 'fiat_currency': 'USD', 'sell_reason': SellType.FORCE_SELL.value, @@ -779,13 +779,13 @@ def test_forcesell_down_handle(default_conf, update, ticker, fee, 'exchange': 'Bittrex', 'pair': 'ETH/BTC', 'gain': 'loss', - 'limit': 1.044e-05, - 'amount': 90.99181073703367, + 'limit': 1.043e-05, + 'amount': 91.07468123861567, 'order_type': 'limit', - 'open_rate': 1.099e-05, - 'current_rate': 1.044e-05, - 'profit_amount': -5.492e-05, - 'profit_percent': -0.05478342, + 'open_rate': 1.098e-05, + 'current_rate': 1.043e-05, + 'profit_amount': -5.497e-05, + 'profit_percent': -0.05482878, 'stake_currency': 'BTC', 'fiat_currency': 'USD', 'sell_reason': SellType.FORCE_SELL.value, @@ -827,13 +827,13 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, mocker) -> None 'exchange': 'Bittrex', 'pair': 'ETH/BTC', 'gain': 'loss', - 'limit': 1.098e-05, - 'amount': 90.99181073703367, + 'limit': 1.099e-05, + 'amount': 91.07468123861567, 'order_type': 'limit', - 'open_rate': 1.099e-05, - 'current_rate': 1.098e-05, - 'profit_amount': -5.91e-06, - 'profit_percent': -0.00589291, + 'open_rate': 1.098e-05, + 'current_rate': 1.099e-05, + 'profit_amount': -4.09e-06, + 'profit_percent': -0.00408133, 'stake_currency': 'BTC', 'fiat_currency': 'USD', 'sell_reason': SellType.FORCE_SELL.value, From e1cb6f4ae3cd1de66df0eb0eb8128b520e170d5c Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 23 Feb 2020 15:06:37 +0100 Subject: [PATCH 050/202] fix and improve tests in test_freqtradebot --- tests/test_freqtradebot.py | 125 ++++++++++++++++++++++--------------- 1 file changed, 74 insertions(+), 51 deletions(-) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 852b6b990..655fe4684 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -761,8 +761,8 @@ def test_process_trade_creation(default_conf, ticker, limit_buy_order, assert trade.is_open assert trade.open_date is not None assert trade.exchange == 'bittrex' - assert trade.open_rate == 0.00001099 - assert trade.amount == 90.99181073703367 + assert trade.open_rate == 0.00001098 + assert trade.amount == 91.07468123861567 assert log_has( 'Buy signal found: about create a new trade with stake_amount: 0.001 ...', caplog @@ -906,20 +906,28 @@ def test_process_informative_pairs_added(default_conf, ticker, mocker) -> None: assert ("ETH/BTC", default_conf["ticker_interval"]) in refresh_mock.call_args[0][0] -@pytest.mark.parametrize("ask,last,last_ab,expected", [ - (20, 10, 0.0, 20), # Full ask side - (20, 10, 1.0, 10), # Full last side - (20, 10, 0.5, 15), # Between ask and last - (20, 10, 0.7, 13), # Between ask and last - (20, 10, 0.3, 17), # Between ask and last - (5, 10, 1.0, 5), # last bigger than ask - (5, 10, 0.5, 5), # last bigger than ask +@pytest.mark.parametrize("side,ask,bid,last,last_ab,expected", [ + ('ask', 20, 19, 10, 0.0, 20), # Full ask side + ('ask', 20, 19, 10, 1.0, 10), # Full last side + ('ask', 20, 19, 10, 0.5, 15), # Between ask and last + ('ask', 20, 19, 10, 0.7, 13), # Between ask and last + ('ask', 20, 19, 10, 0.3, 17), # Between ask and last + ('ask', 5, 6, 10, 1.0, 5), # last bigger than ask + ('ask', 5, 6, 10, 0.5, 5), # last bigger than ask + ('bid', 10, 20, 10, 0.0, 20), # Full bid side + ('bid', 10, 20, 10, 1.0, 10), # Full last side + ('bid', 10, 20, 10, 0.5, 15), # Between bid and last + ('bid', 10, 20, 10, 0.7, 13), # Between bid and last + ('bid', 10, 20, 10, 0.3, 17), # Between bid and last + ('bid', 4, 5, 10, 1.0, 5), # last bigger than bid + ('bid', 4, 5, 10, 0.5, 5), # last bigger than bid ]) -def test_get_buy_rate(mocker, default_conf, caplog, ask, last, last_ab, expected) -> None: +def test_get_buy_rate(mocker, default_conf, caplog, side, ask, bid, last, last_ab, expected) -> None: default_conf['bid_strategy']['ask_last_balance'] = last_ab + default_conf['bid_strategy']['price_side'] = side freqtrade = get_patched_freqtradebot(mocker, default_conf) mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', - MagicMock(return_value={'ask': ask, 'last': last})) + MagicMock(return_value={'ask': ask, 'last': last, 'bid': bid})) assert freqtrade.get_buy_rate('ETH/BTC', True) == expected assert not log_has("Using cached buy rate for ETH/BTC.", caplog) @@ -1317,7 +1325,7 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, caplog, stoploss_order_mock.assert_not_called() assert freqtrade.handle_trade(trade) is False - assert trade.stop_loss == 0.00002344 * 0.95 + assert trade.stop_loss == 0.00002346 * 0.95 # setting stoploss_on_exchange_interval to 0 seconds freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 0 @@ -1325,10 +1333,10 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, caplog, assert freqtrade.handle_stoploss_on_exchange(trade) is False cancel_order_mock.assert_called_once_with(100, 'ETH/BTC') - stoploss_order_mock.assert_called_once_with(amount=85.25149190110828, + stoploss_order_mock.assert_called_once_with(amount=85.32423208191126, pair='ETH/BTC', order_types=freqtrade.strategy.order_types, - stop_price=0.00002344 * 0.95) + stop_price=0.00002346 * 0.95) # price fell below stoploss, so dry-run sells trade. mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', MagicMock(return_value={ @@ -1510,12 +1518,12 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, caplog, assert freqtrade.handle_stoploss_on_exchange(trade) is False # stoploss should be set to 1% as trailing is on - assert trade.stop_loss == 0.00002344 * 0.99 + assert trade.stop_loss == 0.00002346 * 0.99 cancel_order_mock.assert_called_once_with(100, 'NEO/BTC') - stoploss_order_mock.assert_called_once_with(amount=2131074.168797954, + stoploss_order_mock.assert_called_once_with(amount=2132892.491467577, pair='NEO/BTC', order_types=freqtrade.strategy.order_types, - stop_price=0.00002344 * 0.99) + stop_price=0.00002346 * 0.99) def test_enter_positions(mocker, default_conf, caplog) -> None: @@ -2292,12 +2300,12 @@ def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, mocker) -> N 'pair': 'ETH/BTC', 'gain': 'profit', 'limit': 1.172e-05, - 'amount': 90.99181073703367, + 'amount': 91.07468123861567, 'order_type': 'limit', - 'open_rate': 1.099e-05, - 'current_rate': 1.172e-05, - 'profit_amount': 6.126e-05, - 'profit_percent': 0.0611052, + 'open_rate': 1.098e-05, + 'current_rate': 1.173e-05, + 'profit_amount': 6.223e-05, + 'profit_percent': 0.0620716, 'stake_currency': 'BTC', 'fiat_currency': 'USD', 'sell_reason': SellType.ROI.value, @@ -2341,12 +2349,12 @@ def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, mocker) 'pair': 'ETH/BTC', 'gain': 'loss', 'limit': 1.044e-05, - 'amount': 90.99181073703367, + 'amount': 91.07468123861567, 'order_type': 'limit', - 'open_rate': 1.099e-05, - 'current_rate': 1.044e-05, - 'profit_amount': -5.492e-05, - 'profit_percent': -0.05478342, + 'open_rate': 1.098e-05, + 'current_rate': 1.043e-05, + 'profit_amount': -5.406e-05, + 'profit_percent': -0.05392257, 'stake_currency': 'BTC', 'fiat_currency': 'USD', 'sell_reason': SellType.STOP_LOSS.value, @@ -2397,12 +2405,12 @@ def test_execute_sell_down_stoploss_on_exchange_dry_run(default_conf, ticker, fe 'pair': 'ETH/BTC', 'gain': 'loss', 'limit': 1.08801e-05, - 'amount': 90.99181073703367, + 'amount': 91.07468123861567, 'order_type': 'limit', - 'open_rate': 1.099e-05, - 'current_rate': 1.044e-05, - 'profit_amount': -1.498e-05, - 'profit_percent': -0.01493766, + 'open_rate': 1.098e-05, + 'current_rate': 1.043e-05, + 'profit_amount': -1.408e-05, + 'profit_percent': -0.01404051, 'stake_currency': 'BTC', 'fiat_currency': 'USD', 'sell_reason': SellType.STOP_LOSS.value, @@ -2587,7 +2595,7 @@ def test_execute_sell_market_order(default_conf, ticker, fee, freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'], sell_reason=SellType.ROI) assert not trade.is_open - assert trade.close_profit == 0.0611052 + assert trade.close_profit == 0.0620716 assert rpc_mock.call_count == 2 last_msg = rpc_mock.call_args_list[-1][0][0] @@ -2597,12 +2605,12 @@ def test_execute_sell_market_order(default_conf, ticker, fee, 'pair': 'ETH/BTC', 'gain': 'profit', 'limit': 1.172e-05, - 'amount': 90.99181073703367, + 'amount': 91.07468123861567, 'order_type': 'market', - 'open_rate': 1.099e-05, - 'current_rate': 1.172e-05, - 'profit_amount': 6.126e-05, - 'profit_percent': 0.0611052, + 'open_rate': 1.098e-05, + 'current_rate': 1.173e-05, + 'profit_amount': 6.223e-05, + 'profit_percent': 0.0620716, 'stake_currency': 'BTC', 'fiat_currency': 'USD', 'sell_reason': SellType.ROI.value, @@ -3624,13 +3632,20 @@ def test_order_book_ask_strategy(default_conf, limit_buy_order, limit_sell_order assert freqtrade.handle_trade(trade) is True -def test_get_sell_rate(default_conf, mocker, caplog, ticker, order_book_l2) -> None: - - mocker.patch.multiple( - 'freqtrade.exchange.Exchange', - get_order_book=order_book_l2, - fetch_ticker=ticker, - ) +@pytest.mark.parametrize('side,ask,bid,expected', [ + ('bid', 10.0, 11.0, 11.0), + ('bid', 10.0, 11.2, 11.2), + ('bid', 10.0, 11.0, 11.0), + ('bid', 9.8, 11.0, 11.0), + ('bid', 0.0001, 0.002, 0.002), + ('ask', 10.0, 11.0, 10.0), + ('ask', 10.11, 11.2, 10.11), + ('ask', 0.001, 0.002, 0.001), + ('ask', 0.006, 1.0, 0.006), +]) +def test_get_sell_rate(default_conf, mocker, caplog, side, bid, ask, expected) -> None: + default_conf['ask_strategy']['price_side'] = side + mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', return_value={'ask': ask, 'bid': bid}) pair = "ETH/BTC" # Test regular mode @@ -3638,25 +3653,33 @@ def test_get_sell_rate(default_conf, mocker, caplog, ticker, order_book_l2) -> N rate = ft.get_sell_rate(pair, True) assert not log_has("Using cached sell rate for ETH/BTC.", caplog) assert isinstance(rate, float) - assert rate == 0.00001098 + assert rate == expected # Use caching rate = ft.get_sell_rate(pair, False) - assert rate == 0.00001098 + assert rate == expected assert log_has("Using cached sell rate for ETH/BTC.", caplog) - caplog.clear() +@pytest.mark.parametrize('side,expected', [ + ('bid', 0.043936), # Value from order_book_l2 fiture - bids side + ('ask', 0.043949), # Value from order_book_l2 fiture - asks side +]) +def test_get_sell_rate_orderbook(default_conf, mocker, caplog, side, expected, order_book_l2): # Test orderbook mode + default_conf['ask_strategy']['price_side'] = side default_conf['ask_strategy']['use_order_book'] = True default_conf['ask_strategy']['order_book_min'] = 1 default_conf['ask_strategy']['order_book_max'] = 2 + # TODO: min/max is irrelevant for this test until refactoring + pair = "ETH/BTC" + mocker.patch('freqtrade.exchange.Exchange.get_order_book', order_book_l2) ft = get_patched_freqtradebot(mocker, default_conf) rate = ft.get_sell_rate(pair, True) assert not log_has("Using cached sell rate for ETH/BTC.", caplog) assert isinstance(rate, float) - assert rate == 0.043936 + assert rate == expected rate = ft.get_sell_rate(pair, False) - assert rate == 0.043936 + assert rate == expected assert log_has("Using cached sell rate for ETH/BTC.", caplog) From 8edc3eb5fb8a6c4dc62f455f12d237c0d670b3d9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 23 Feb 2020 15:39:12 +0100 Subject: [PATCH 051/202] Use generator to generate sell price scaffold testing --- freqtrade/freqtradebot.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 2c9a960a4..22a73a273 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -619,6 +619,15 @@ class FreqtradeBot: return trades_closed + def _order_book_gen(self, pair: str, side: str, order_book_max: int = 1, + order_book_min: int = 1): + """ + Helper generator to query orderbook in loop (used for early sell-order placing) + """ + order_book = self.exchange.get_order_book(pair, order_book_max) + for i in range(order_book_min, order_book_max + 1): + yield order_book[side][i - 1][0] + def get_sell_rate(self, pair: str, refresh: bool) -> float: """ Get sell rate - either using get-ticker bid or first bid based on orderbook @@ -639,9 +648,10 @@ class FreqtradeBot: config_ask_strategy = self.config.get('ask_strategy', {}) if config_ask_strategy.get('use_order_book', False): logger.debug('Using order book to get sell rate') + rate = next(self._order_book_gen(pair, f"{config_ask_strategy['price_side']}s")) - order_book = self.exchange.get_order_book(pair, 1) - rate = order_book[f"{config_ask_strategy['price_side']}s"][0][0] + # order_book = self.exchange.get_order_book(pair, 1) + # rate = order_book[f"{config_ask_strategy['price_side']}s"][0][0] else: rate = self.exchange.fetch_ticker(pair)[config_ask_strategy['price_side']] @@ -674,12 +684,12 @@ class FreqtradeBot: order_book_min = config_ask_strategy.get('order_book_min', 1) order_book_max = config_ask_strategy.get('order_book_max', 1) - order_book = self.exchange.get_order_book(trade.pair, order_book_max) - + order_book = self._order_book_gen(trade.pair, f"{config_ask_strategy['price_side']}s", + order_book_min=order_book_min, + order_book_max=order_book_max) for i in range(order_book_min, order_book_max + 1): - order_book_rate = order_book['asks'][i - 1][0] - logger.debug(' order book asks top %s: %0.8f', i, order_book_rate) - sell_rate = order_book_rate + sell_rate = next(order_book) + logger.debug(' order book asks top %s: %0.8f', i, sell_rate) if self._check_and_execute_sell(trade, sell_rate, buy, sell): return True From 3c5e716d8f5b3c527ae7683877fb10149c03ebe2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 23 Feb 2020 16:00:34 +0100 Subject: [PATCH 052/202] Update some documentation regarding price_side --- docs/configuration.md | 64 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 57 insertions(+), 7 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index b05dab7c9..54470f278 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -60,11 +60,13 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `trailing_only_offset_is_reached` | Only apply trailing stoploss when the offset is reached. [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy).
*Defaults to `false`.*
**Datatype:** Boolean | `unfilledtimeout.buy` | **Required.** How long (in minutes) the bot will wait for an unfilled buy order to complete, after which the order will be cancelled. [Strategy Override](#parameters-in-the-strategy).
**Datatype:** Integer | `unfilledtimeout.sell` | **Required.** How long (in minutes) the bot will wait for an unfilled sell order to complete, after which the order will be cancelled. [Strategy Override](#parameters-in-the-strategy).
**Datatype:** Integer -| `bid_strategy.ask_last_balance` | **Required.** Set the bidding price. More information [below](#buy-price-without-orderbook). +| `bid_strategy.price_side` | Select the side of the spread the bot should look at to get the buy rate. [More information below](#buy-price-side).
*Defaults to `bid`.*
**Datatype:** String (either `ask` or `bid`). +| `bid_strategy.ask_last_balance` | **Required.** Set the bidding price. More information [below](#buy-price-without-orderbook-enabled). | `bid_strategy.use_order_book` | Enable buying using the rates in [Order Book Bids](#buy-price-with-orderbook-enabled).
**Datatype:** Boolean | `bid_strategy.order_book_top` | Bot will use the top N rate in Order Book Bids to buy. I.e. a value of 2 will allow the bot to pick the 2nd bid rate in [Order Book Bids](#buy-price-with-orderbook-enabled).
*Defaults to `1`.*
**Datatype:** Positive Integer | `bid_strategy. check_depth_of_market.enabled` | Do not buy if the difference of buy orders and sell orders is met in Order Book. [Check market depth](#check-depth-of-market).
*Defaults to `false`.*
**Datatype:** Boolean | `bid_strategy. check_depth_of_market.bids_to_ask_delta` | The difference ratio of buy orders and sell orders found in Order Book. A value below 1 means sell order size is greater, while value greater than 1 means buy order size is higher. [Check market depth](#check-depth-of-market)
*Defaults to `0`.*
**Datatype:** Float (as ratio) +| `ask_strategy.price_side` | Select the side of the spread the bot should look at to get the sell rate. [More information below](#sell-price-side).
*Defaults to `ask`.*
**Datatype:** String (either `ask` or `bid`). | `ask_strategy.use_order_book` | Enable selling of open trades using [Order Book Asks](#sell-price-with-orderbook-enabled).
**Datatype:** Boolean | `ask_strategy.order_book_min` | Bot will scan from the top min to max Order Book Asks searching for a profitable rate.
*Defaults to `1`.*
**Datatype:** Positive Integer | `ask_strategy.order_book_max` | Bot will scan from the top min to max Order Book Asks searching for a profitable rate.
*Defaults to `1`.*
**Datatype:** Positive Integer @@ -461,23 +463,71 @@ Orderbook `bid` (buy) side depth is then divided by the orderbook `ask` (sell) s !!! Note A delta value below 1 means that `ask` (sell) orderbook side depth is greater than the depth of the `bid` (buy) orderbook side, while a value greater than 1 means opposite (depth of the buy side is higher than the depth of the sell side). +#### Buy price side + +The configuration option `bid_strategy.price_side` defines the side of the spread the bot looks for when buying. + +The following displays an orderbook. + +``` explanation +... +103 +102 +101 # ask +-------------Current spread +99 # bid +98 +97 +... +``` + +If `bid_strategy.price_side` is set to `"bid"`, then the bot will use 99 as buying price. +In line with that, if `bid_strategy.price_side` is set to `"ask"`, then the bot will use 101 as buying price. + +Using `ask` price often guarantees quicker filled orders, but the bot can also end up paying more than what would have been necessary. + + #### Buy price with Orderbook enabled -When buying with the orderbook enabled (`bid_strategy.use_order_book=True`), Freqtrade fetches the `bid_strategy.order_book_top` entries from the orderbook and then uses the entry specified as `bid_strategy.order_book_top` on the `bid` (buy) side of the orderbook. 1 specifies the topmost entry in the orderbook, while 2 would use the 2nd entry in the orderbook, and so on. +When buying with the orderbook enabled (`bid_strategy.use_order_book=True`), Freqtrade fetches the `bid_strategy.order_book_top` entries from the orderbook and then uses the entry specified as `bid_strategy.order_book_top` on the configured side (`bid_strategy.price_side`) of the orderbook. 1 specifies the topmost entry in the orderbook, while 2 would use the 2nd entry in the orderbook, and so on. #### Buy price without Orderbook enabled -When not using orderbook (`bid_strategy.use_order_book=False`), Freqtrade uses the best `ask` (sell) price from the ticker if it's below the `last` traded price from the ticker. Otherwise (when the `ask` price is not below the `last` price), it calculates a rate between `ask` and `last` price. +The following section uses `side` as the configured `bid_strategy.price_side`. -The `bid_strategy.ask_last_balance` configuration parameter controls this. A value of `0.0` will use `ask` price, while `1.0` will use the `last` price and values between those interpolate between ask and last price. +When not using orderbook (`bid_strategy.use_order_book=False`), Freqtrade uses the best `side` price from the ticker if it's below the `last` traded price from the ticker. Otherwise (when the `side` price is above the `last` price), it calculates a rate between `side` and `last` price. -Using `ask` price often guarantees quicker success in the bid, but the bot can also end up paying more than what would have been necessary. +The `bid_strategy.ask_last_balance` configuration parameter controls this. A value of `0.0` will use `side` price, while `1.0` will use the `last` price and values between those interpolate between ask and last price. ### Sell price +#### Sell price side + +The configuration option `ask_strategy.price_side` defines the side of the spread the bot looks for when selling. + +The following displays an orderbook: + +``` explanation +... +103 +102 +101 # ask +-------------Current spread +99 # bid +98 +97 +... +``` + +If `ask_strategy.price_side` is set to `"ask"`, then the bot will use 101 as selling price. +In line with that, if `ask_strategy.price_side` is set to `"bid"`, then the bot will use 99 as selling price. + #### Sell price with Orderbook enabled -When selling with the orderbook enabled (`ask_strategy.use_order_book=True`), Freqtrade fetches the `ask_strategy.order_book_max` entries in the orderbook. Then each of the orderbook steps between `ask_strategy.order_book_min` and `ask_strategy.order_book_max` on the `ask` orderbook side are validated for a profitable sell-possibility based on the strategy configuration and the sell order is placed at the first profitable spot. +When selling with the orderbook enabled (`ask_strategy.use_order_book=True`), Freqtrade fetches the `ask_strategy.order_book_max` entries in the orderbook. Then each of the orderbook steps between `ask_strategy.order_book_min` and `ask_strategy.order_book_max` on the configured orderbook side are validated for a profitable sell-possibility based on the strategy configuration and the sell order is placed at the first profitable spot. + +!!! Note: + Using `order_book_max` higher than `order_book_min` only makes sense when ask_strategy.price_side is set to `"ask"`. The idea here is to place the sell order early, to be ahead in the queue. @@ -488,7 +538,7 @@ A fixed slot (mirroring `bid_strategy.order_book_top`) can be defined by setting #### Sell price without Orderbook enabled -When not using orderbook (`ask_strategy.use_order_book=False`), the `bid` price from the ticker will be used as the sell price. +When not using orderbook (`ask_strategy.use_order_book=False`), the price at the `ask_strategy.price_side` side (defaults to `"ask"`) from the ticker will be used as the sell price. ## Pairlists From 0fea3a7ea74451243a9518d3059599d15ea7a9aa Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 26 Feb 2020 19:49:54 +0100 Subject: [PATCH 053/202] Some final polish to configurable_side --- docs/configuration.md | 2 +- freqtrade/freqtradebot.py | 4 +--- tests/test_freqtradebot.py | 3 ++- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 54470f278..c70f3425c 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -526,7 +526,7 @@ In line with that, if `ask_strategy.price_side` is set to `"bid"`, then the bot When selling with the orderbook enabled (`ask_strategy.use_order_book=True`), Freqtrade fetches the `ask_strategy.order_book_max` entries in the orderbook. Then each of the orderbook steps between `ask_strategy.order_book_min` and `ask_strategy.order_book_max` on the configured orderbook side are validated for a profitable sell-possibility based on the strategy configuration and the sell order is placed at the first profitable spot. -!!! Note: +!!! Note Using `order_book_max` higher than `order_book_min` only makes sense when ask_strategy.price_side is set to `"ask"`. The idea here is to place the sell order early, to be ahead in the queue. diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 22a73a273..53493ad4c 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -647,12 +647,10 @@ class FreqtradeBot: config_ask_strategy = self.config.get('ask_strategy', {}) if config_ask_strategy.get('use_order_book', False): + # This code is only used for notifications, selling uses the generator directly logger.debug('Using order book to get sell rate') rate = next(self._order_book_gen(pair, f"{config_ask_strategy['price_side']}s")) - # order_book = self.exchange.get_order_book(pair, 1) - # rate = order_book[f"{config_ask_strategy['price_side']}s"][0][0] - else: rate = self.exchange.fetch_ticker(pair)[config_ask_strategy['price_side']] self._sell_rate_cache[pair] = rate diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 655fe4684..49000382f 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -922,7 +922,8 @@ def test_process_informative_pairs_added(default_conf, ticker, mocker) -> None: ('bid', 4, 5, 10, 1.0, 5), # last bigger than bid ('bid', 4, 5, 10, 0.5, 5), # last bigger than bid ]) -def test_get_buy_rate(mocker, default_conf, caplog, side, ask, bid, last, last_ab, expected) -> None: +def test_get_buy_rate(mocker, default_conf, caplog, side, ask, bid, + last, last_ab, expected) -> None: default_conf['bid_strategy']['ask_last_balance'] = last_ab default_conf['bid_strategy']['price_side'] = side freqtrade = get_patched_freqtradebot(mocker, default_conf) From b6839289ec2cba080c3bfeb2d956e1e70660770e Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 26 Feb 2020 20:03:03 +0100 Subject: [PATCH 054/202] Add price_side to sample config files --- config_full.json.example | 2 ++ freqtrade/templates/base_config.json.j2 | 2 ++ 2 files changed, 4 insertions(+) diff --git a/config_full.json.example b/config_full.json.example index cdb7e841e..f0414bd0d 100644 --- a/config_full.json.example +++ b/config_full.json.example @@ -25,6 +25,7 @@ "sell": 30 }, "bid_strategy": { + "price_side": "bid", "use_order_book": false, "ask_last_balance": 0.0, "order_book_top": 1, @@ -34,6 +35,7 @@ } }, "ask_strategy":{ + "price_side": "ask", "use_order_book": false, "order_book_min": 1, "order_book_max": 9, diff --git a/freqtrade/templates/base_config.json.j2 b/freqtrade/templates/base_config.json.j2 index 88edeb1e8..0049d59a0 100644 --- a/freqtrade/templates/base_config.json.j2 +++ b/freqtrade/templates/base_config.json.j2 @@ -11,6 +11,7 @@ "sell": 30 }, "bid_strategy": { + "price_side": "bid", "ask_last_balance": 0.0, "use_order_book": false, "order_book_top": 1, @@ -20,6 +21,7 @@ } }, "ask_strategy": { + "price_side": "ask", "use_order_book": false, "order_book_min": 1, "order_book_max": 9, From e5ec97495ddbf331fe43a0ed55a2f938d343d943 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 27 Feb 2020 07:01:00 +0100 Subject: [PATCH 055/202] Logging should be initialized first --- freqtrade/configuration/configuration.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/freqtrade/configuration/configuration.py b/freqtrade/configuration/configuration.py index 21b3e3bd3..035b091b3 100644 --- a/freqtrade/configuration/configuration.py +++ b/freqtrade/configuration/configuration.py @@ -96,6 +96,8 @@ class Configuration: # Keep a copy of the original configuration file config['original_config'] = deepcopy(config) + self._process_logging_options(config) + self._process_runmode(config) self._process_common_options(config) @@ -146,8 +148,6 @@ class Configuration: def _process_common_options(self, config: Dict[str, Any]) -> None: - self._process_logging_options(config) - # Set strategy if not specified in config and or if it's non default if self.args.get("strategy") or not config.get('strategy'): config.update({'strategy': self.args.get("strategy")}) @@ -379,7 +379,7 @@ class Configuration: if not self.runmode: # Handle real mode, infer dry/live from config self.runmode = RunMode.DRY_RUN if config.get('dry_run', True) else RunMode.LIVE - logger.info(f"Runmode set to {self.runmode}.") + logger.info(f"Runmode set to {self.runmode.value}.") config.update({'runmode': self.runmode}) From bbb438bd4093d38e8e41e05fd151ec1fa2799411 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 27 Feb 2020 06:18:09 +0000 Subject: [PATCH 056/202] Bump python from 3.8.1-slim-buster to 3.8.2-slim-buster Bumps python from 3.8.1-slim-buster to 3.8.2-slim-buster. Signed-off-by: dependabot-preview[bot] --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 923285f39..d986f20ae 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.8.1-slim-buster +FROM python:3.8.2-slim-buster RUN apt-get update \ && apt-get -y install curl build-essential libssl-dev \ From 55d471190a3afc52984edab5035b460abd429434 Mon Sep 17 00:00:00 2001 From: Fredrik81 Date: Thu, 27 Feb 2020 13:28:28 +0100 Subject: [PATCH 057/202] Changed table style of backtesting and alignment of headers --- freqtrade/optimize/backtesting.py | 47 +++++++++++++++---------- freqtrade/optimize/optimize_reports.py | 8 ++--- tests/optimize/test_optimize_reports.py | 43 +++++++++++----------- 3 files changed, 53 insertions(+), 45 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index c18aefc76..94441ce24 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -423,28 +423,37 @@ class Backtesting: strategy if len(self.strategylist) > 1 else None) print(f"Result for strategy {strategy}") - print(' BACKTESTING REPORT '.center(133, '=')) - print(generate_text_table(data, - stake_currency=self.config['stake_currency'], - max_open_trades=self.config['max_open_trades'], - results=results)) + table = generate_text_table(data, stake_currency=self.config['stake_currency'], + max_open_trades=self.config['max_open_trades'], + results=results) + if isinstance(table, str): + print(' BACKTESTING REPORT '.center(len(table.splitlines()[0]), '=')) + print(table) - print(' SELL REASON STATS '.center(133, '=')) - print(generate_text_table_sell_reason(data, - stake_currency=self.config['stake_currency'], - max_open_trades=self.config['max_open_trades'], - results=results)) + table = generate_text_table_sell_reason(data, + stake_currency=self.config['stake_currency'], + max_open_trades=self.config['max_open_trades'], + results=results) + if isinstance(table, str): + print(' SELL REASON STATS '.center(len(table.splitlines()[0]), '=')) + print(table) - print(' LEFT OPEN TRADES REPORT '.center(133, '=')) - print(generate_text_table(data, - stake_currency=self.config['stake_currency'], - max_open_trades=self.config['max_open_trades'], - results=results.loc[results.open_at_end], skip_nan=True)) + table = generate_text_table(data, + stake_currency=self.config['stake_currency'], + max_open_trades=self.config['max_open_trades'], + results=results.loc[results.open_at_end], skip_nan=True) + if isinstance(table, str): + print(' LEFT OPEN TRADES REPORT '.center(len(table.splitlines()[0]), '=')) + print(table) + if isinstance(table, str): + print('=' * len(table.splitlines()[0])) print() if len(all_results) > 1: # Print Strategy summary table - print(' STRATEGY SUMMARY '.center(133, '=')) - print(generate_text_table_strategy(self.config['stake_currency'], - self.config['max_open_trades'], - all_results=all_results)) + table = generate_text_table_strategy(self.config['stake_currency'], + self.config['max_open_trades'], + all_results=all_results) + print(' STRATEGY SUMMARY '.center(len(table.splitlines()[0]), '=')) + print(table) + print('=' * len(table.splitlines()[0])) print('\nFor more details, please look at the detail tables above') diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index b00adbd48..39bde50a8 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -66,7 +66,7 @@ def generate_text_table(data: Dict[str, Dict], stake_currency: str, max_open_tra ]) # Ignore type as floatfmt does allow tuples but mypy does not know that return tabulate(tabular_data, headers=headers, - floatfmt=floatfmt, tablefmt="pipe") # type: ignore + floatfmt=floatfmt, tablefmt="orgtbl", stralign="right") # type: ignore def generate_text_table_sell_reason( @@ -112,7 +112,7 @@ def generate_text_table_sell_reason( profit_percent_tot, ] ) - return tabulate(tabular_data, headers=headers, tablefmt="pipe") + return tabulate(tabular_data, headers=headers, tablefmt="orgtbl", stralign="right") def generate_text_table_strategy(stake_currency: str, max_open_trades: str, @@ -146,7 +146,7 @@ def generate_text_table_strategy(stake_currency: str, max_open_trades: str, ]) # Ignore type as floatfmt does allow tuples but mypy does not know that return tabulate(tabular_data, headers=headers, - floatfmt=floatfmt, tablefmt="pipe") # type: ignore + floatfmt=floatfmt, tablefmt="orgtbl", stralign="right") # type: ignore def generate_edge_table(results: dict) -> str: @@ -172,4 +172,4 @@ def generate_edge_table(results: dict) -> str: # Ignore type as floatfmt does allow tuples but mypy does not know that return tabulate(tabular_data, headers=headers, - floatfmt=floatfmt, tablefmt="pipe") # type: ignore + floatfmt=floatfmt, tablefmt="orgtbl", stralign="right") # type: ignore diff --git a/tests/optimize/test_optimize_reports.py b/tests/optimize/test_optimize_reports.py index 57e928cca..285ecaa02 100644 --- a/tests/optimize/test_optimize_reports.py +++ b/tests/optimize/test_optimize_reports.py @@ -22,14 +22,14 @@ def test_generate_text_table(default_conf, mocker): ) result_str = ( - '| Pair | Buys | Avg Profit % | Cum Profit % | Tot Profit BTC |' - ' Tot Profit % | Avg Duration | Wins | Draws | Losses |\n' - '|:--------|-------:|---------------:|---------------:|-----------------:|' - '---------------:|:---------------|-------:|--------:|---------:|\n' + '| Pair | Buys | Avg Profit % | Cum Profit % | Tot Profit BTC |' + ' Tot Profit % | Avg Duration | Wins | Draws | Losses |\n' + '|---------+--------+----------------+----------------+------------------+' + '----------------+----------------+--------+---------+----------|\n' '| ETH/BTC | 2 | 15.00 | 30.00 | 0.60000000 |' - ' 15.00 | 0:20:00 | 2 | 0 | 0 |\n' - '| TOTAL | 2 | 15.00 | 30.00 | 0.60000000 |' - ' 15.00 | 0:20:00 | 2 | 0 | 0 |' + ' 15.00 | 0:20:00 | 2 | 0 | 0 |\n' + '| TOTAL | 2 | 15.00 | 30.00 | 0.60000000 |' + ' 15.00 | 0:20:00 | 2 | 0 | 0 |' ) assert generate_text_table(data={'ETH/BTC': {}}, stake_currency='BTC', max_open_trades=2, @@ -52,13 +52,13 @@ def test_generate_text_table_sell_reason(default_conf, mocker): ) result_str = ( - '| Sell Reason | Sells | Wins | Draws | Losses |' + '| Sell Reason | Sells | Wins | Draws | Losses |' ' Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % |\n' - '|:--------------|--------:|-------:|--------:|---------:|' - '---------------:|---------------:|-----------------:|---------------:|\n' - '| roi | 2 | 2 | 0 | 0 |' + '|---------------+---------+--------+---------+----------+' + '----------------+----------------+------------------+----------------|\n' + '| roi | 2 | 2 | 0 | 0 |' ' 15 | 30 | 0.6 | 15 |\n' - '| stop_loss | 1 | 0 | 0 | 1 |' + '| stop_loss | 1 | 0 | 0 | 1 |' ' -10 | -10 | -0.2 | -5 |' ) assert generate_text_table_sell_reason( @@ -95,14 +95,14 @@ def test_generate_text_table_strategy(default_conf, mocker): ) result_str = ( - '| Strategy | Buys | Avg Profit % | Cum Profit % | Tot' - ' Profit BTC | Tot Profit % | Avg Duration | Wins | Draws | Losses |\n' - '|:--------------|-------:|---------------:|---------------:|------' - '-----------:|---------------:|:---------------|-------:|--------:|---------:|\n' - '| TestStrategy1 | 3 | 20.00 | 60.00 | ' - ' 1.10000000 | 30.00 | 0:17:00 | 3 | 0 | 0 |\n' - '| TestStrategy2 | 3 | 30.00 | 90.00 | ' - ' 1.30000000 | 45.00 | 0:20:00 | 3 | 0 | 0 |' + '| Strategy | Buys | Avg Profit % | Cum Profit % | Tot' + ' Profit BTC | Tot Profit % | Avg Duration | Wins | Draws | Losses |\n' + '|---------------+--------+----------------+----------------+------------------+' + '----------------+----------------+--------+---------+----------|\n' + '| TestStrategy1 | 3 | 20.00 | 60.00 | 1.10000000 |' + ' 30.00 | 0:17:00 | 3 | 0 | 0 |\n' + '| TestStrategy2 | 3 | 30.00 | 90.00 | 1.30000000 |' + ' 45.00 | 0:20:00 | 3 | 0 | 0 |' ) assert generate_text_table_strategy('BTC', 2, all_results=results) == result_str @@ -111,8 +111,7 @@ def test_generate_edge_table(edge_conf, mocker): results = {} results['ETH/BTC'] = PairInfo(-0.01, 0.60, 2, 1, 3, 10, 60) - - assert generate_edge_table(results).count(':|') == 7 + assert generate_edge_table(results).count('+') == 7 assert generate_edge_table(results).count('| ETH/BTC |') == 1 assert generate_edge_table(results).count( '| Risk Reward Ratio | Required Risk Reward | Expectancy |') == 1 From e5a9c81412b4a6846872fde59489203184244288 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 27 Feb 2020 14:04:12 +0100 Subject: [PATCH 058/202] Add 0 entry to enhanced ROI example --- docs/strategy-customization.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/strategy-customization.md b/docs/strategy-customization.md index fd40a971e..4aacd3af6 100644 --- a/docs/strategy-customization.md +++ b/docs/strategy-customization.md @@ -257,11 +257,12 @@ from freqtrade.exchange import timeframe_to_minutes class AwesomeStrategy(IStrategy): - ticker_interval = '1d' + ticker_interval = "1d" ticker_interval_mins = timeframe_to_minutes(ticker_interval) minimal_roi = { - str(ticker_interval_mins * 3)): 0.02, # After 3 candles - str(ticker_interval_mins * 6)): 0.01, # After 6 candles + "0": 0.05, # 5% for the first 3 candles + str(ticker_interval_mins * 3)): 0.02, # 2% after 3 candles + str(ticker_interval_mins * 6)): 0.01, # 1% After 6 candles } ``` From 15e59654d9121bbbe8a1a20f4d1e0529906cc18d Mon Sep 17 00:00:00 2001 From: Fredrik81 Date: Thu, 27 Feb 2020 16:10:45 +0100 Subject: [PATCH 059/202] Minor change to standardize table style. This PR will target commands. --- freqtrade/commands/list_commands.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/commands/list_commands.py b/freqtrade/commands/list_commands.py index 49674b81a..327901dc0 100644 --- a/freqtrade/commands/list_commands.py +++ b/freqtrade/commands/list_commands.py @@ -58,7 +58,7 @@ def _print_objs_tabular(objs: List, print_colorized: bool) -> None: else yellow + "DUPLICATE NAME" + reset) } for s in objs] - print(tabulate(objss_to_print, headers='keys', tablefmt='pipe')) + print(tabulate(objss_to_print, headers='keys', tablefmt='psql', stralign='right')) def start_list_strategies(args: Dict[str, Any]) -> None: @@ -192,7 +192,7 @@ def start_list_markets(args: Dict[str, Any], pairs_only: bool = False) -> None: else: # print data as a table, with the human-readable summary print(f"{summary_str}:") - print(tabulate(tabular_data, headers='keys', tablefmt='pipe')) + print(tabulate(tabular_data, headers='keys', tablefmt='psql', stralign='right')) elif not (args.get('print_one_column', False) or args.get('list_pairs_print_json', False) or args.get('print_csv', False)): From 5a02026f82decd18789f78440e4a71b51e24ff89 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 27 Feb 2020 19:35:58 +0100 Subject: [PATCH 060/202] Add test validating behaviour --- tests/test_configuration.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_configuration.py b/tests/test_configuration.py index a58e88ea0..1e9d6440d 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -319,6 +319,7 @@ def test_load_dry_run(default_conf, mocker, config_value, expected, arglist) -> validated_conf = configuration.load_config() assert validated_conf['dry_run'] is expected + assert validated_conf['runmode'] == (RunMode.DRY_RUN if expected else RunMode.LIVE) def test_load_custom_strategy(default_conf, mocker) -> None: From a55964a6221c1806ba195bf7be1dfe4167fd15f2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 27 Feb 2020 19:36:54 +0100 Subject: [PATCH 061/202] we Must parse --dry-run before setting run-mode --- freqtrade/configuration/configuration.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/freqtrade/configuration/configuration.py b/freqtrade/configuration/configuration.py index 035b091b3..6a0441957 100644 --- a/freqtrade/configuration/configuration.py +++ b/freqtrade/configuration/configuration.py @@ -167,10 +167,6 @@ class Configuration: if 'sd_notify' in self.args and self.args["sd_notify"]: config['internals'].update({'sd_notify': True}) - self._args_to_config(config, argname='dry_run', - logstring='Parameter --dry-run detected, ' - 'overriding dry_run to: {} ...') - def _process_datadir_options(self, config: Dict[str, Any]) -> None: """ Extract information for sys.argv and load directory configurations @@ -376,6 +372,10 @@ class Configuration: def _process_runmode(self, config: Dict[str, Any]) -> None: + self._args_to_config(config, argname='dry_run', + logstring='Parameter --dry-run detected, ' + 'overriding dry_run to: {} ...') + if not self.runmode: # Handle real mode, infer dry/live from config self.runmode = RunMode.DRY_RUN if config.get('dry_run', True) else RunMode.LIVE From e411717de9397afe29b58c62a7aca5c3aa6f8f65 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Fri, 28 Feb 2020 12:36:39 +0300 Subject: [PATCH 062/202] No percent where ratio is to be used --- freqtrade/edge/edge_positioning.py | 12 ++++++------ freqtrade/freqtradebot.py | 12 ++++++------ freqtrade/persistence.py | 4 ++-- freqtrade/rpc/rpc.py | 20 ++++++++++---------- freqtrade/rpc/telegram.py | 2 +- freqtrade/strategy/interface.py | 7 ++++--- tests/edge/test_edge.py | 2 +- tests/rpc/test_rpc_telegram.py | 12 ++++++------ tests/test_freqtradebot.py | 8 ++++---- 9 files changed, 40 insertions(+), 39 deletions(-) diff --git a/freqtrade/edge/edge_positioning.py b/freqtrade/edge/edge_positioning.py index ee5c3e95d..57a8f4a7c 100644 --- a/freqtrade/edge/edge_positioning.py +++ b/freqtrade/edge/edge_positioning.py @@ -246,7 +246,8 @@ class Edge: # we set stake amount to an arbitrary amount. # as it doesn't change the calculation. - # all returned values are relative. they are percentages. + # all returned values are relative. + # they are defined as ratios. stake = 0.015 fee = self.fee open_fee = fee / 2 @@ -269,8 +270,8 @@ class Edge: result['sell_fee'] = result['sell_sum'] * close_fee result['sell_take'] = result['sell_sum'] - result['sell_fee'] - # profit_percent - result['profit_percent'] = (result['sell_take'] - result['buy_spend']) / result['buy_spend'] + # profit_ratio + result['profit_ratio'] = (result['sell_take'] - result['buy_spend']) / result['buy_spend'] # Absolute profit result['profit_abs'] = result['sell_take'] - result['buy_spend'] @@ -399,9 +400,8 @@ class Edge: # trade opens in reality on the next candle open_trade_index += 1 - stop_price_percentage = stoploss + 1 open_price = ohlc_columns[open_trade_index, 0] - stop_price = (open_price * stop_price_percentage) + stop_price = (open_price * (stoploss + 1)) # Searching for the index where stoploss is hit stop_index = utf1st.find_1st( @@ -441,7 +441,7 @@ class Edge: trade = {'pair': pair, 'stoploss': stoploss, - 'profit_percent': '', + 'profit_ratio': '', 'profit_abs': '', 'open_time': date_column[open_trade_index], 'close_time': date_column[exit_index], diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index dffec940c..f50244dac 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -1034,8 +1034,8 @@ class FreqtradeBot: profit_trade = trade.calc_profit(rate=profit_rate) # Use cached ticker here - it was updated seconds ago. current_rate = self.get_sell_rate(trade.pair, False) - profit_percent = trade.calc_profit_ratio(profit_rate) - gain = "profit" if profit_percent > 0 else "loss" + profit_ratio = trade.calc_profit_ratio(profit_rate) + gain = "profit" if profit_ratio > 0 else "loss" msg = { 'type': RPCMessageType.SELL_NOTIFICATION, @@ -1048,7 +1048,7 @@ class FreqtradeBot: 'open_rate': trade.open_rate, 'current_rate': current_rate, 'profit_amount': profit_trade, - 'profit_percent': profit_percent, + 'profit_ratio': profit_ratio, 'sell_reason': trade.sell_reason, 'open_date': trade.open_date, 'close_date': trade.close_date or datetime.utcnow(), @@ -1071,8 +1071,8 @@ class FreqtradeBot: profit_rate = trade.close_rate if trade.close_rate else trade.close_rate_requested profit_trade = trade.calc_profit(rate=profit_rate) current_rate = self.get_sell_rate(trade.pair, False) - profit_percent = trade.calc_profit_ratio(profit_rate) - gain = "profit" if profit_percent > 0 else "loss" + profit_ratio = trade.calc_profit_ratio(profit_rate) + gain = "profit" if profit_ratio > 0 else "loss" msg = { 'type': RPCMessageType.SELL_CANCEL_NOTIFICATION, @@ -1085,7 +1085,7 @@ class FreqtradeBot: 'open_rate': trade.open_rate, 'current_rate': current_rate, 'profit_amount': profit_trade, - 'profit_percent': profit_percent, + 'profit_ratio': profit_ratio, 'sell_reason': trade.sell_reason, 'open_date': trade.open_date, 'close_date': trade.close_date, diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index fa041abc3..ac084d12e 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -405,8 +405,8 @@ class Trade(_DECL_BASE): rate=(rate or self.close_rate), fee=(fee or self.fee_close) ) - profit_percent = (close_trade_price / self.open_trade_price) - 1 - return float(f"{profit_percent:.8f}") + profit_ratio = (close_trade_price / self.open_trade_price) - 1 + return float(f"{profit_ratio:.8f}") @staticmethod def get_trades(trade_filter=None) -> Query: diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 1e4eaa3e0..9014c1874 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -155,9 +155,9 @@ class RPC: current_rate = self._freqtrade.get_sell_rate(trade.pair, False) except DependencyException: current_rate = NAN - trade_perc = (100 * trade.calc_profit_ratio(current_rate)) + trade_percent = (100 * trade.calc_profit_ratio(current_rate)) trade_profit = trade.calc_profit(current_rate) - profit_str = f'{trade_perc:.2f}%' + profit_str = f'{trade_percent:.2f}%' if self._fiat_converter: fiat_profit = self._fiat_converter.convert_amount( trade_profit, @@ -232,9 +232,9 @@ class RPC: trades = Trade.get_trades().order_by(Trade.id).all() profit_all_coin = [] - profit_all_perc = [] + profit_all_ratio = [] profit_closed_coin = [] - profit_closed_perc = [] + profit_closed_ratio = [] durations = [] for trade in trades: @@ -246,21 +246,21 @@ class RPC: durations.append((trade.close_date - trade.open_date).total_seconds()) if not trade.is_open: - profit_percent = trade.calc_profit_ratio() + profit_ratio = trade.calc_profit_ratio() profit_closed_coin.append(trade.calc_profit()) - profit_closed_perc.append(profit_percent) + profit_closed_ratio.append(profit_ratio) else: # Get current rate try: current_rate = self._freqtrade.get_sell_rate(trade.pair, False) except DependencyException: current_rate = NAN - profit_percent = trade.calc_profit_ratio(rate=current_rate) + profit_ratio = trade.calc_profit_ratio(rate=current_rate) profit_all_coin.append( trade.calc_profit(rate=trade.close_rate or current_rate) ) - profit_all_perc.append(profit_percent) + profit_all_ratio.append(profit_ratio) best_pair = Trade.get_best_pair() @@ -271,7 +271,7 @@ class RPC: # Prepare data to display profit_closed_coin_sum = round(sum(profit_closed_coin), 8) - profit_closed_percent = (round(mean(profit_closed_perc) * 100, 2) if profit_closed_perc + profit_closed_percent = (round(mean(profit_closed_ratio) * 100, 2) if profit_closed_ratio else 0.0) profit_closed_fiat = self._fiat_converter.convert_amount( profit_closed_coin_sum, @@ -280,7 +280,7 @@ class RPC: ) if self._fiat_converter else 0 profit_all_coin_sum = round(sum(profit_all_coin), 8) - profit_all_percent = round(mean(profit_all_perc) * 100, 2) if profit_all_perc else 0.0 + profit_all_percent = round(mean(profit_all_ratio) * 100, 2) if profit_all_ratio else 0.0 profit_all_fiat = self._fiat_converter.convert_amount( profit_all_coin_sum, stake_currency, diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index e3958b31a..ad01700ab 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -148,7 +148,7 @@ class Telegram(RPC): elif msg['type'] == RPCMessageType.SELL_NOTIFICATION: msg['amount'] = round(msg['amount'], 8) - msg['profit_percent'] = round(msg['profit_percent'] * 100, 2) + msg['profit_percent'] = round(msg['profit_ratio'] * 100, 2) msg['duration'] = msg['close_date'].replace( microsecond=0) - msg['open_date'].replace(microsecond=0) msg['duration_min'] = msg['duration'].total_seconds() / 60 diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index ae3dbd307..d23af3f6e 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -364,7 +364,7 @@ class IStrategy(ABC): """ Based on current profit of the trade and configured (trailing) stoploss, decides to sell or not - :param current_profit: current profit in percent + :param current_profit: current profit as ratio """ stop_loss_value = force_stoploss if force_stoploss else self.stoploss @@ -427,8 +427,9 @@ class IStrategy(ABC): def min_roi_reached(self, trade: Trade, current_profit: float, current_time: datetime) -> bool: """ - Based on trade duration, current price and ROI configuration, decides whether bot should - sell. Requires current_profit to be in percent!! + Based on trade duration, current profit of the trade and ROI configuration, + decides whether bot should sell. + :param current_profit: current profit as ratio :return: True if bot should sell at current rate """ # Check if time matches and current rate is above threshold diff --git a/tests/edge/test_edge.py b/tests/edge/test_edge.py index 6b86d9c1f..2a0d19128 100644 --- a/tests/edge/test_edge.py +++ b/tests/edge/test_edge.py @@ -158,7 +158,7 @@ def test_edge_results(edge_conf, mocker, caplog, data) -> None: assert len(trades) == len(data.trades) if not results.empty: - assert round(results["profit_percent"].sum(), 3) == round(data.profit_perc, 3) + assert round(results["profit_ratio"].sum(), 3) == round(data.profit_perc, 3) for c, trade in enumerate(data.trades): res = results.iloc[c] diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index a8b8e0c5a..1ac03f812 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -726,7 +726,7 @@ def test_forcesell_handle(default_conf, update, ticker, fee, 'open_rate': 1.099e-05, 'current_rate': 1.172e-05, 'profit_amount': 6.126e-05, - 'profit_percent': 0.0611052, + 'profit_ratio': 0.0611052, 'stake_currency': 'BTC', 'fiat_currency': 'USD', 'sell_reason': SellType.FORCE_SELL.value, @@ -785,7 +785,7 @@ def test_forcesell_down_handle(default_conf, update, ticker, fee, 'open_rate': 1.099e-05, 'current_rate': 1.044e-05, 'profit_amount': -5.492e-05, - 'profit_percent': -0.05478342, + 'profit_ratio': -0.05478342, 'stake_currency': 'BTC', 'fiat_currency': 'USD', 'sell_reason': SellType.FORCE_SELL.value, @@ -833,7 +833,7 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, mocker) -> None 'open_rate': 1.099e-05, 'current_rate': 1.098e-05, 'profit_amount': -5.91e-06, - 'profit_percent': -0.00589291, + 'profit_ratio': -0.00589291, 'stake_currency': 'BTC', 'fiat_currency': 'USD', 'sell_reason': SellType.FORCE_SELL.value, @@ -1253,7 +1253,7 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None: 'open_rate': 7.5e-05, 'current_rate': 3.201e-05, 'profit_amount': -0.05746268, - 'profit_percent': -0.57405275, + 'profit_ratio': -0.57405275, 'stake_currency': 'ETH', 'fiat_currency': 'USD', 'sell_reason': SellType.STOP_LOSS.value, @@ -1282,7 +1282,7 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None: 'open_rate': 7.5e-05, 'current_rate': 3.201e-05, 'profit_amount': -0.05746268, - 'profit_percent': -0.57405275, + 'profit_ratio': -0.57405275, 'stake_currency': 'ETH', 'sell_reason': SellType.STOP_LOSS.value, 'open_date': arrow.utcnow().shift(days=-1, hours=-2, minutes=-30), @@ -1448,7 +1448,7 @@ def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None: 'open_rate': 7.5e-05, 'current_rate': 3.201e-05, 'profit_amount': -0.05746268, - 'profit_percent': -0.57405275, + 'profit_ratio': -0.57405275, 'stake_currency': 'ETH', 'fiat_currency': 'USD', 'sell_reason': SellType.STOP_LOSS.value, diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 852b6b990..61f69bd85 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -2297,7 +2297,7 @@ def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, mocker) -> N 'open_rate': 1.099e-05, 'current_rate': 1.172e-05, 'profit_amount': 6.126e-05, - 'profit_percent': 0.0611052, + 'profit_ratio': 0.0611052, 'stake_currency': 'BTC', 'fiat_currency': 'USD', 'sell_reason': SellType.ROI.value, @@ -2346,7 +2346,7 @@ def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, mocker) 'open_rate': 1.099e-05, 'current_rate': 1.044e-05, 'profit_amount': -5.492e-05, - 'profit_percent': -0.05478342, + 'profit_ratio': -0.05478342, 'stake_currency': 'BTC', 'fiat_currency': 'USD', 'sell_reason': SellType.STOP_LOSS.value, @@ -2402,7 +2402,7 @@ def test_execute_sell_down_stoploss_on_exchange_dry_run(default_conf, ticker, fe 'open_rate': 1.099e-05, 'current_rate': 1.044e-05, 'profit_amount': -1.498e-05, - 'profit_percent': -0.01493766, + 'profit_ratio': -0.01493766, 'stake_currency': 'BTC', 'fiat_currency': 'USD', 'sell_reason': SellType.STOP_LOSS.value, @@ -2602,7 +2602,7 @@ def test_execute_sell_market_order(default_conf, ticker, fee, 'open_rate': 1.099e-05, 'current_rate': 1.172e-05, 'profit_amount': 6.126e-05, - 'profit_percent': 0.0611052, + 'profit_ratio': 0.0611052, 'stake_currency': 'BTC', 'fiat_currency': 'USD', 'sell_reason': SellType.ROI.value, 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 063/202] 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) From 349aa2f9574d04f7f20c9c04e6008767129a17e5 Mon Sep 17 00:00:00 2001 From: Fredrik81 Date: Fri, 28 Feb 2020 21:54:04 +0100 Subject: [PATCH 064/202] Added dynamic print table function to hyperopt --- freqtrade/commands/hyperopt_commands.py | 2 +- freqtrade/optimize/hyperopt.py | 22 +++++++++++++++------- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/freqtrade/commands/hyperopt_commands.py b/freqtrade/commands/hyperopt_commands.py index ccaa59e54..72e5bf12f 100755 --- a/freqtrade/commands/hyperopt_commands.py +++ b/freqtrade/commands/hyperopt_commands.py @@ -51,7 +51,7 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None: try: Hyperopt.print_result_table(config, trials, total_epochs, - not filteroptions['only_best'], print_colorized) + not filteroptions['only_best'], print_colorized, 0) except KeyboardInterrupt: print('User interrupted..') diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 66ea18bd1..84cd078a0 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -116,6 +116,7 @@ class Hyperopt: self.config['ask_strategy']['use_sell_signal'] = True self.print_all = self.config.get('print_all', False) + self.hyperopt_table_header = 0 self.print_colorized = self.config.get('print_colorized', False) self.print_json = self.config.get('print_json', False) @@ -272,8 +273,13 @@ class Hyperopt: if not self.print_all: # Separate the results explanation string from dots print("\n") - self.print_results_explanation(results, self.total_epochs, self.print_all, - self.print_colorized) + self.print_result_table(self.config, results, self.total_epochs, + self.print_all, self.print_colorized, + self.hyperopt_table_header) + if is_best: + self.hyperopt_table_header = 2 + else: + self.hyperopt_table_header = 3 @staticmethod def print_results_explanation(results, total_epochs, highlight_best: bool, @@ -299,7 +305,7 @@ class Hyperopt: @staticmethod def print_result_table(config: dict, results: list, total_epochs: int, highlight_best: bool, - print_colorized: bool) -> None: + print_colorized: bool, remove_header: int) -> None: """ Log result table """ @@ -328,7 +334,7 @@ class Hyperopt: trials['Profit'] = trials['Profit'].apply( lambda x: '{:,.2f}%'.format(x) if not isna(x) else x) trials['Total profit'] = trials['Total profit'].apply( - lambda x: '{: 11.8f} '.format(x) + config['stake_currency'] if not isna(x) else x) + lambda x: '{:,.8f} '.format(x) + config['stake_currency'] if not isna(x) else x) trials['Avg duration'] = trials['Avg duration'].apply( lambda x: '{:,.1f}m'.format(x) if not isna(x) else x) if print_colorized: @@ -343,9 +349,11 @@ class Hyperopt: str(trials.loc[i][z]), Style.RESET_ALL) trials = trials.drop(columns=['is_initial_point', 'is_best', 'is_profit']) - - print(tabulate(trials.to_dict(orient='list'), headers='keys', tablefmt='psql', - stralign="right")) + table = tabulate(trials.to_dict(orient='list'), tablefmt='psql', + headers='keys', stralign="right") + if remove_header > 0: + table = table.split("\n", remove_header)[remove_header] + print(table) def has_space(self, space: str) -> bool: """ From 5277d71913566088747c6499740bcdc68ac51f27 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 29 Feb 2020 14:56:04 +0100 Subject: [PATCH 065/202] Add test for empty stake-currency --- tests/exchange/test_exchange.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 179566bb0..6bec53d49 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -511,6 +511,22 @@ def test_validate_pairs_stakecompatibility(default_conf, mocker, caplog): Exchange(default_conf) +def test_validate_pairs_stakecompatibility_downloaddata(default_conf, mocker, caplog): + api_mock = MagicMock() + default_conf['stake_currency'] = '' + type(api_mock).markets = PropertyMock(return_value={ + 'ETH/BTC': {'quote': 'BTC'}, 'LTC/BTC': {'quote': 'BTC'}, + 'XRP/BTC': {'quote': 'BTC'}, 'NEO/BTC': {'quote': 'BTC'}, + 'HELLO-WORLD': {'quote': 'BTC'}, + }) + mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) + mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') + mocker.patch('freqtrade.exchange.Exchange._load_async_markets') + mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') + + Exchange(default_conf) + + def test_validate_pairs_stakecompatibility_fail(default_conf, mocker, caplog): default_conf['exchange']['pair_whitelist'].append('HELLO-WORLD') api_mock = MagicMock() From 60579485e52dd36d1bc20344766baf9850510b57 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 29 Feb 2020 14:56:36 +0100 Subject: [PATCH 066/202] fix empty stake currency problem --- freqtrade/exchange/exchange.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 799ee2c37..f6b722c9a 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -332,7 +332,8 @@ class Exchange: logger.warning(f"Pair {pair} is restricted for some users on this exchange." f"Please check if you are impacted by this restriction " f"on the exchange and eventually remove {pair} from your whitelist.") - if not self.get_pair_quote_currency(pair) == self._config['stake_currency']: + if (self._config['stake_currency'] and + not self.get_pair_quote_currency(pair) == self._config['stake_currency']): invalid_pairs.append(pair) if invalid_pairs: raise OperationalException( From 9336d8ee02d63e56513fe0a76c5bbef00aab9390 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 29 Feb 2020 15:44:45 +0100 Subject: [PATCH 067/202] Try fix random testfailure --- tests/commands/test_commands.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index 5f9bc0aa2..1877aaa43 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -447,11 +447,9 @@ def test_create_datadir_failed(caplog): def test_create_datadir(caplog, mocker): - # Ensure that caplog is empty before starting ... - # Should prevent random failures. - caplog.clear() - # Added assert here to analyze random test-failures ... - assert len(caplog.record_tuples) == 0 + + # Capture caplog length here trying to avoid random test failure + len_caplog_before = len(caplog.record_tuples) cud = mocker.patch("freqtrade.commands.deploy_commands.create_userdata_dir", MagicMock()) csf = mocker.patch("freqtrade.commands.deploy_commands.copy_sample_files", MagicMock()) @@ -464,7 +462,7 @@ def test_create_datadir(caplog, mocker): assert cud.call_count == 1 assert csf.call_count == 1 - assert len(caplog.record_tuples) == 0 + assert len(caplog.record_tuples) == len_caplog_before def test_start_new_strategy(mocker, caplog): From f25adf3b12096fab33dbcb098a8d679067334a17 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 29 Feb 2020 15:48:36 +0100 Subject: [PATCH 068/202] improve and correct release documentation --- docs/developer.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/docs/developer.md b/docs/developer.md index b128ffd2b..ef9232a59 100644 --- a/docs/developer.md +++ b/docs/developer.md @@ -234,7 +234,7 @@ git checkout -b new_release Determine if crucial bugfixes have been made between this commit and the current state, and eventually cherry-pick these. -* Edit `freqtrade/__init__.py` and add the version matching the current date (for example `2019.7` for July 2019). Minor versions can be `2019.7-1` should we need to do a second release that month. +* Edit `freqtrade/__init__.py` and add the version matching the current date (for example `2019.7` for July 2019). Minor versions can be `2019.7.1` should we need to do a second release that month. Version numbers must follow allowed versions from PEP0440 to avoid failures pushing to pypi. * Commit this part * push that branch to the remote and create a PR against the master branch @@ -268,11 +268,6 @@ Once the PR against master is merged (best right after merging): * Use "master" as reference (this step comes after the above PR is merged). * Use the above changelog as release comment (as codeblock) -### After-release - -* Update version in develop by postfixing that with `-dev` (`2019.6 -> 2019.6-dev`). -* Create a PR against develop to update that branch. - ## Releases ### pypi From 848054d140243a18e2dfa8eb70077e6be15a4f94 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 29 Feb 2020 15:53:54 +0100 Subject: [PATCH 069/202] Fix jupyter notebook example - generate_candlestick_graph() needs a filtered pairlist, not a list containing all pairs --- docs/strategy_analysis_example.md | 6 ++++-- freqtrade/templates/strategy_analysis_example.ipynb | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/strategy_analysis_example.md b/docs/strategy_analysis_example.md index 53b35ca09..d26d684ce 100644 --- a/docs/strategy_analysis_example.md +++ b/docs/strategy_analysis_example.md @@ -121,7 +121,6 @@ from freqtrade.data.btanalysis import analyze_trade_parallelism # Analyze the above parallel_trades = analyze_trade_parallelism(trades, '5m') - parallel_trades.plot() ``` @@ -134,11 +133,14 @@ Freqtrade offers interactive plotting capabilities based on plotly. from freqtrade.plot.plotting import generate_candlestick_graph # Limit graph period to keep plotly quick and reactive +# Filter trades to one pair +trades_red = trades.loc[trades['pair'] == pair] + data_red = data['2019-06-01':'2019-06-10'] # Generate candlestick graph graph = generate_candlestick_graph(pair=pair, data=data_red, - trades=trades, + trades=trades_red, indicators1=['sma20', 'ema50', 'ema55'], indicators2=['rsi', 'macd', 'macdsignal', 'macdhist'] ) diff --git a/freqtrade/templates/strategy_analysis_example.ipynb b/freqtrade/templates/strategy_analysis_example.ipynb index 399235cfe..dffa308ce 100644 --- a/freqtrade/templates/strategy_analysis_example.ipynb +++ b/freqtrade/templates/strategy_analysis_example.ipynb @@ -190,7 +190,6 @@ "# Analyze the above\n", "parallel_trades = analyze_trade_parallelism(trades, '5m')\n", "\n", - "\n", "parallel_trades.plot()" ] }, @@ -212,11 +211,14 @@ "from freqtrade.plot.plotting import generate_candlestick_graph\n", "# Limit graph period to keep plotly quick and reactive\n", "\n", + "# Filter trades to one pair\n", + "trades_red = trades.loc[trades['pair'] == pair]\n", + "\n", "data_red = data['2019-06-01':'2019-06-10']\n", "# Generate candlestick graph\n", "graph = generate_candlestick_graph(pair=pair,\n", " data=data_red,\n", - " trades=trades,\n", + " trades=trades_red,\n", " indicators1=['sma20', 'ema50', 'ema55'],\n", " indicators2=['rsi', 'macd', 'macdsignal', 'macdhist']\n", " )\n", From 4c39f360844a5db0352914e190a7a933489adb4d Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 29 Feb 2020 16:36:33 +0100 Subject: [PATCH 070/202] Add note about InvalidNonce to documentation --- docs/exchanges.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/exchanges.md b/docs/exchanges.md index f615bc61a..70dae0aa5 100644 --- a/docs/exchanges.md +++ b/docs/exchanges.md @@ -62,6 +62,11 @@ res = [ f"{x['MarketCurrency']}/{x['BaseCurrency']}" for x in ct.publicGetMarket print(res) ``` +## All exchanges + +Should you experience constant errors with Nonce (like `InvalidNonce`), it is best to regenerate the API keys. Resetting Nonce is difficult and it's usually easier to regenerate the API keys. + + ## Random notes for other exchanges * The Ocean (exchange id: `theocean`) exchange uses Web3 functionality and requires `web3` python package to be installed: From d7373be55344f80883203fa493f766d542911882 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 29 Feb 2020 16:58:22 +0100 Subject: [PATCH 071/202] Add official support for Kraken --- README.md | 3 ++- docs/configuration.md | 5 ++++- freqtrade/exchange/exchange.py | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 59799da84..88070d45e 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,8 @@ hesitate to read the source code and understand the mechanism of this bot. ## Exchange marketplaces supported - [X] [Bittrex](https://bittrex.com/) -- [X] [Binance](https://www.binance.com/) ([*Note for binance users](#a-note-on-binance)) +- [X] [Binance](https://www.binance.com/) ([*Note for binance users](docs/exchanges.md#blacklists)) +- [X] [Kraken](https://kraken.com/) - [ ] [113 others to tests](https://github.com/ccxt/ccxt/). _(We cannot guarantee they will work)_ ## Documentation diff --git a/docs/configuration.md b/docs/configuration.md index b05dab7c9..95af2c049 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -371,15 +371,18 @@ The possible values are: `gtc` (default), `fok` or `ioc`. Freqtrade is based on [CCXT library](https://github.com/ccxt/ccxt) that supports over 100 cryptocurrency exchange markets and trading APIs. The complete up-to-date list can be found in the [CCXT repo homepage](https://github.com/ccxt/ccxt/tree/master/python). However, the bot was tested -with only Bittrex and Binance. +with only Bittrex, Binance and Kraken. The bot was tested with the following exchanges: - [Bittrex](https://bittrex.com/): "bittrex" - [Binance](https://www.binance.com/): "binance" +- [Kraken](https://kraken.com/): "kraken" Feel free to test other exchanges and submit your PR to improve the bot. +Some exchanges require special configuration, which can be found on the [Exchange-specific Notes](exchanges.md) documentation page. + #### Sample exchange configuration A exchange configuration for "binance" would look as follows: diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 799ee2c37..4f03bde80 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -1023,7 +1023,7 @@ def is_exchange_known_ccxt(exchange_name: str, ccxt_module: CcxtModuleType = Non def is_exchange_officially_supported(exchange_name: str) -> bool: - return exchange_name in ['bittrex', 'binance'] + return exchange_name in ['bittrex', 'binance', 'kraken'] def ccxt_exchanges(ccxt_module: CcxtModuleType = None) -> List[str]: From 18d724f7a18994ba4bf50b605218f9ee76e5750a Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 29 Feb 2020 19:22:36 +0100 Subject: [PATCH 072/202] Adjust wording of supported exchanges --- docs/configuration.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 95af2c049..b3f032bc6 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -370,10 +370,9 @@ The possible values are: `gtc` (default), `fok` or `ioc`. Freqtrade is based on [CCXT library](https://github.com/ccxt/ccxt) that supports over 100 cryptocurrency exchange markets and trading APIs. The complete up-to-date list can be found in the -[CCXT repo homepage](https://github.com/ccxt/ccxt/tree/master/python). However, the bot was tested -with only Bittrex, Binance and Kraken. - -The bot was tested with the following exchanges: +[CCXT repo homepage](https://github.com/ccxt/ccxt/tree/master/python). + However, the bot was tested by the development team with only Bittrex, Binance and Kraken, + so the these are the only officially supported exhanges: - [Bittrex](https://bittrex.com/): "bittrex" - [Binance](https://www.binance.com/): "binance" From 60f04cff4de955361b310295b702150e256b8789 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 29 Feb 2020 20:41:03 +0100 Subject: [PATCH 073/202] Simplify expression --- freqtrade/exchange/exchange.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index f6b722c9a..d1397a282 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -333,7 +333,7 @@ class Exchange: f"Please check if you are impacted by this restriction " f"on the exchange and eventually remove {pair} from your whitelist.") if (self._config['stake_currency'] and - not self.get_pair_quote_currency(pair) == self._config['stake_currency']): + self.get_pair_quote_currency(pair) != self._config['stake_currency']): invalid_pairs.append(pair) if invalid_pairs: raise OperationalException( From 23ae0653bd6a92ccdfc0a513dde1fa7f120ceb49 Mon Sep 17 00:00:00 2001 From: Fredrik81 Date: Sat, 29 Feb 2020 23:24:08 +0100 Subject: [PATCH 074/202] Changed table output to match hyperopt-list command --- freqtrade/optimize/hyperopt.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 84cd078a0..700614453 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -276,10 +276,7 @@ class Hyperopt: self.print_result_table(self.config, results, self.total_epochs, self.print_all, self.print_colorized, self.hyperopt_table_header) - if is_best: - self.hyperopt_table_header = 2 - else: - self.hyperopt_table_header = 3 + self.hyperopt_table_header = 2 @staticmethod def print_results_explanation(results, total_epochs, highlight_best: bool, @@ -349,10 +346,17 @@ class Hyperopt: str(trials.loc[i][z]), Style.RESET_ALL) trials = trials.drop(columns=['is_initial_point', 'is_best', 'is_profit']) - table = tabulate(trials.to_dict(orient='list'), tablefmt='psql', - headers='keys', stralign="right") if remove_header > 0: + table = tabulate(trials.to_dict(orient='list'), tablefmt='orgtbl', + headers='keys', stralign="right") table = table.split("\n", remove_header)[remove_header] + elif remove_header < 0: + table = tabulate(trials.to_dict(orient='list'), tablefmt='psql', + headers='keys', stralign="right") + table = "\n".join(table.split("\n")[0:remove_header]) + else: + table = tabulate(trials.to_dict(orient='list'), tablefmt='psql', + headers='keys', stralign="right") print(table) def has_space(self, space: str) -> bool: @@ -541,7 +545,7 @@ class Hyperopt: def start(self) -> None: self.random_state = self._set_random_state(self.config.get('hyperopt_random_state', None)) logger.info(f"Using optimizer random state: {self.random_state}") - + self.hyperopt_table_header = -1 data, timerange = self.backtesting.load_bt_data() preprocessed = self.backtesting.strategy.tickerdata_to_dataframe(data) From 7a4edb1cd8f0c397bd16b19bcb67c4e0b7f40fd9 Mon Sep 17 00:00:00 2001 From: Fredrik81 Date: Sat, 29 Feb 2020 23:41:59 +0100 Subject: [PATCH 075/202] Fix: When total epochs is less than cpu cores --- freqtrade/optimize/hyperopt.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 66ea18bd1..ee3f4d2c4 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -557,6 +557,8 @@ class Hyperopt: cpus = cpu_count() logger.info(f"Found {cpus} CPU cores. Let's make them scream!") config_jobs = self.config.get('hyperopt_jobs', -1) + if self.total_epochs < cpus: + config_jobs = self.total_epochs logger.info(f'Number of parallel jobs set as: {config_jobs}') self.dimensions: List[Dimension] = self.hyperopt_space() From e89fd33229e0b8c6e9ebbd8e49920205fcf0a81c Mon Sep 17 00:00:00 2001 From: Fredrik81 Date: Sat, 29 Feb 2020 23:57:15 +0100 Subject: [PATCH 076/202] Fix for more arguments --- freqtrade/optimize/hyperopt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index ee3f4d2c4..6b334507a 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -557,7 +557,7 @@ class Hyperopt: cpus = cpu_count() logger.info(f"Found {cpus} CPU cores. Let's make them scream!") config_jobs = self.config.get('hyperopt_jobs', -1) - if self.total_epochs < cpus: + if self.total_epochs < cpus and (config_jobs > self.total_epochs or config_jobs < 0): config_jobs = self.total_epochs logger.info(f'Number of parallel jobs set as: {config_jobs}') From 267416ecedf34c4019cac403fe18f808c38a2b06 Mon Sep 17 00:00:00 2001 From: Fredrik81 Date: Sun, 1 Mar 2020 03:11:00 +0100 Subject: [PATCH 077/202] Changed test for new table printing --- tests/optimize/test_hyperopt.py | 129 +++++++++++++++++++++++++++----- 1 file changed, 109 insertions(+), 20 deletions(-) diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index e3212e0cd..0fa5b9536 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -390,17 +390,30 @@ def test_onlyprofit_loss_prefers_higher_profits(default_conf, hyperopt_results) def test_log_results_if_loss_improves(hyperopt, capsys) -> None: hyperopt.current_best_loss = 2 hyperopt.total_epochs = 2 + hyperopt.print_results( { - 'is_best': True, 'loss': 1, + 'results_metrics': + { + 'trade_count': 1, + 'avg_profit': 0.1, + 'total_profit': 0.001, + 'profit': 1.0, + 'duration': 20.0 + }, + 'total_profit': 0, 'current_epoch': 2, # This starts from 1 (in a human-friendly manner) - 'results_explanation': 'foo.', - 'is_initial_point': False + 'is_initial_point': False, + 'is_best': True } ) out, err = capsys.readouterr() - assert ' 2/2: foo. Objective: 1.00000' in out + result_str = ( + '| Best | 2/2 | 1 | 0.10% | 0.00100000 BTC |' + ' 1.00% | 20.0m | 1 |' + ) + assert result_str in out def test_no_log_if_loss_does_not_improve(hyperopt, caplog) -> None: @@ -467,7 +480,16 @@ def test_start_calls_optimizer(mocker, default_conf, caplog, capsys) -> None: parallel = mocker.patch( 'freqtrade.optimize.hyperopt.Hyperopt.run_optimizer_parallel', MagicMock(return_value=[{'loss': 1, 'results_explanation': 'foo result', - 'params': {'buy': {}, 'sell': {}, 'roi': {}, 'stoploss': 0.0}}]) + 'params': {'buy': {}, 'sell': {}, 'roi': {}, 'stoploss': 0.0}, + 'results_metrics': + { + 'trade_count': 1, + 'avg_profit': 0.1, + 'total_profit': 0.001, + 'profit': 1.0, + 'duration': 20.0 + }, + }]) ) patch_exchange(mocker) # Co-test loading ticker-interval from strategy @@ -761,11 +783,23 @@ def test_print_json_spaces_all(mocker, default_conf, caplog, capsys) -> None: parallel = mocker.patch( 'freqtrade.optimize.hyperopt.Hyperopt.run_optimizer_parallel', - MagicMock(return_value=[{'loss': 1, 'results_explanation': 'foo result', 'params': {}, - 'params_details': {'buy': {'mfi-value': None}, - 'sell': {'sell-mfi-value': None}, - 'roi': {}, 'stoploss': {'stoploss': None}, - 'trailing': {'trailing_stop': None}}}]) + MagicMock(return_value=[{ + 'loss': 1, 'results_explanation': 'foo result', 'params': {}, + 'params_details': { + 'buy': {'mfi-value': None}, + 'sell': {'sell-mfi-value': None}, + 'roi': {}, 'stoploss': {'stoploss': None}, + 'trailing': {'trailing_stop': None} + }, + 'results_metrics': + { + 'trade_count': 1, + 'avg_profit': 0.1, + 'total_profit': 0.001, + 'profit': 1.0, + 'duration': 20.0 + } + }]) ) patch_exchange(mocker) @@ -787,7 +821,11 @@ def test_print_json_spaces_all(mocker, default_conf, caplog, capsys) -> None: parallel.assert_called_once() out, err = capsys.readouterr() - assert '{"params":{"mfi-value":null,"sell-mfi-value":null},"minimal_roi":{},"stoploss":null,"trailing_stop":null}' in out # noqa: E501 + result_str = ( + '{"params":{"mfi-value":null,"sell-mfi-value":null},"minimal_roi"' + ':{},"stoploss":null,"trailing_stop":null}' + ) + assert result_str in out # noqa: E501 assert dumper.called # Should be called twice, once for tickerdata, once to save evaluations assert dumper.call_count == 2 @@ -804,10 +842,22 @@ def test_print_json_spaces_default(mocker, default_conf, caplog, capsys) -> None parallel = mocker.patch( 'freqtrade.optimize.hyperopt.Hyperopt.run_optimizer_parallel', - MagicMock(return_value=[{'loss': 1, 'results_explanation': 'foo result', 'params': {}, - 'params_details': {'buy': {'mfi-value': None}, - 'sell': {'sell-mfi-value': None}, - 'roi': {}, 'stoploss': {'stoploss': None}}}]) + MagicMock(return_value=[{ + 'loss': 1, 'results_explanation': 'foo result', 'params': {}, + 'params_details': { + 'buy': {'mfi-value': None}, + 'sell': {'sell-mfi-value': None}, + 'roi': {}, 'stoploss': {'stoploss': None} + }, + 'results_metrics': + { + 'trade_count': 1, + 'avg_profit': 0.1, + 'total_profit': 0.001, + 'profit': 1.0, + 'duration': 20.0 + } + }]) ) patch_exchange(mocker) @@ -846,8 +896,18 @@ def test_print_json_spaces_roi_stoploss(mocker, default_conf, caplog, capsys) -> parallel = mocker.patch( 'freqtrade.optimize.hyperopt.Hyperopt.run_optimizer_parallel', - MagicMock(return_value=[{'loss': 1, 'results_explanation': 'foo result', 'params': {}, - 'params_details': {'roi': {}, 'stoploss': {'stoploss': None}}}]) + MagicMock(return_value=[{ + 'loss': 1, 'results_explanation': 'foo result', 'params': {}, + 'params_details': {'roi': {}, 'stoploss': {'stoploss': None}}, + 'results_metrics': + { + 'trade_count': 1, + 'avg_profit': 0.1, + 'total_profit': 0.001, + 'profit': 1.0, + 'duration': 20.0 + } + }]) ) patch_exchange(mocker) @@ -887,7 +947,16 @@ def test_simplified_interface_roi_stoploss(mocker, default_conf, caplog, capsys) parallel = mocker.patch( 'freqtrade.optimize.hyperopt.Hyperopt.run_optimizer_parallel', MagicMock(return_value=[{ - 'loss': 1, 'results_explanation': 'foo result', 'params': {'stoploss': 0.0}}]) + 'loss': 1, 'results_explanation': 'foo result', 'params': {'stoploss': 0.0}, + 'results_metrics': + { + 'trade_count': 1, + 'avg_profit': 0.1, + 'total_profit': 0.001, + 'profit': 1.0, + 'duration': 20.0 + } + }]) ) patch_exchange(mocker) @@ -965,7 +1034,17 @@ def test_simplified_interface_buy(mocker, default_conf, caplog, capsys) -> None: parallel = mocker.patch( 'freqtrade.optimize.hyperopt.Hyperopt.run_optimizer_parallel', - MagicMock(return_value=[{'loss': 1, 'results_explanation': 'foo result', 'params': {}}]) + MagicMock(return_value=[{ + 'loss': 1, 'results_explanation': 'foo result', 'params': {}, + 'results_metrics': + { + 'trade_count': 1, + 'avg_profit': 0.1, + 'total_profit': 0.001, + 'profit': 1.0, + 'duration': 20.0 + } + }]) ) patch_exchange(mocker) @@ -1012,7 +1091,17 @@ def test_simplified_interface_sell(mocker, default_conf, caplog, capsys) -> None parallel = mocker.patch( 'freqtrade.optimize.hyperopt.Hyperopt.run_optimizer_parallel', - MagicMock(return_value=[{'loss': 1, 'results_explanation': 'foo result', 'params': {}}]) + MagicMock(return_value=[{ + 'loss': 1, 'results_explanation': 'foo result', 'params': {}, + 'results_metrics': + { + 'trade_count': 1, + 'avg_profit': 0.1, + 'total_profit': 0.001, + 'profit': 1.0, + 'duration': 20.0 + } + }]) ) patch_exchange(mocker) From 379275e2d6446f8f5950a61b0b233933c851f632 Mon Sep 17 00:00:00 2001 From: Fredrik81 Date: Sun, 1 Mar 2020 03:24:04 +0100 Subject: [PATCH 078/202] Updated tests --- tests/optimize/test_hyperopt.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index 0fa5b9536..5bd9e542f 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -479,17 +479,18 @@ def test_start_calls_optimizer(mocker, default_conf, caplog, capsys) -> None: parallel = mocker.patch( 'freqtrade.optimize.hyperopt.Hyperopt.run_optimizer_parallel', - MagicMock(return_value=[{'loss': 1, 'results_explanation': 'foo result', - 'params': {'buy': {}, 'sell': {}, 'roi': {}, 'stoploss': 0.0}, - 'results_metrics': - { - 'trade_count': 1, - 'avg_profit': 0.1, - 'total_profit': 0.001, - 'profit': 1.0, - 'duration': 20.0 - }, - }]) + MagicMock(return_value=[{ + 'loss': 1, 'results_explanation': 'foo result', + 'params': {'buy': {}, 'sell': {}, 'roi': {}, 'stoploss': 0.0}, + 'results_metrics': + { + 'trade_count': 1, + 'avg_profit': 0.1, + 'total_profit': 0.001, + 'profit': 1.0, + 'duration': 20.0 + }, + }]) ) patch_exchange(mocker) # Co-test loading ticker-interval from strategy From 75b4f1a442e29084c4a93bda81419fe4223cd33d Mon Sep 17 00:00:00 2001 From: Fredrik81 Date: Sun, 1 Mar 2020 14:12:27 +0100 Subject: [PATCH 079/202] Fix alignment of higher values --- freqtrade/optimize/hyperopt.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 6b334507a..947aa78bf 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -9,6 +9,7 @@ import logging import random import sys import warnings +from math import ceil, floor from collections import OrderedDict from operator import itemgetter from pathlib import Path @@ -571,7 +572,7 @@ class Hyperopt: with Parallel(n_jobs=config_jobs) as parallel: jobs = parallel._effective_n_jobs() logger.info(f'Effective number of parallel workers used: {jobs}') - EVALS = max(self.total_epochs // jobs, 1) + EVALS = ceil(self.total_epochs / jobs) for i in range(EVALS): asked = self.opt.ask(n_points=jobs) f_val = self.run_optimizer_parallel(parallel, asked, i) @@ -580,6 +581,8 @@ class Hyperopt: for j in range(jobs): # Use human-friendly indexes here (starting from 1) current = i * jobs + j + 1 + if current > self.total_epochs: + continue val = f_val[j] val['current_epoch'] = current val['is_initial_point'] = current <= INITIAL_POINTS From f08c7eedf1b583a65e01d4ea5f30ead17f7bfad3 Mon Sep 17 00:00:00 2001 From: Fredrik81 Date: Sun, 1 Mar 2020 14:35:13 +0100 Subject: [PATCH 080/202] Changed jobs to be dynamic for last loop --- freqtrade/optimize/hyperopt.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 947aa78bf..91507c347 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -9,7 +9,7 @@ import logging import random import sys import warnings -from math import ceil, floor +from math import ceil from collections import OrderedDict from operator import itemgetter from pathlib import Path @@ -578,11 +578,13 @@ class Hyperopt: f_val = self.run_optimizer_parallel(parallel, asked, i) self.opt.tell(asked, [v['loss'] for v in f_val]) self.fix_optimizer_models_list() - for j in range(jobs): + if (i * jobs + jobs) > self.total_epochs: + current_jobs = jobs - ((i * jobs + jobs) - self.total_epochs) + else: + current_jobs = jobs + for j in range(current_jobs): # Use human-friendly indexes here (starting from 1) current = i * jobs + j + 1 - if current > self.total_epochs: - continue val = f_val[j] val['current_epoch'] = current val['is_initial_point'] = current <= INITIAL_POINTS From 77b7f95efb786c71e325cc662dea0bb6bc61d291 Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Mon, 2 Mar 2020 00:14:01 +0100 Subject: [PATCH 081/202] simple code styling fixes --- freqtrade/commands/hyperopt_commands.py | 56 ++++++++++++------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/freqtrade/commands/hyperopt_commands.py b/freqtrade/commands/hyperopt_commands.py index ccaa59e54..e7f89a375 100755 --- a/freqtrade/commands/hyperopt_commands.py +++ b/freqtrade/commands/hyperopt_commands.py @@ -97,10 +97,10 @@ def start_hyperopt_show(args: Dict[str, Any]) -> None: if n > trials_epochs: raise OperationalException( - f"The index of the epoch to show should be less than {trials_epochs + 1}.") + f"The index of the epoch to show should be less than {trials_epochs + 1}.") if n < -trials_epochs: raise OperationalException( - f"The index of the epoch to show should be greater than {-trials_epochs - 1}.") + f"The index of the epoch to show should be greater than {-trials_epochs - 1}.") # Translate epoch index from human-readable format to pythonic if n > 0: @@ -122,52 +122,52 @@ def _hyperopt_filter_trials(trials: List, filteroptions: dict) -> List: trials = [x for x in trials if x['results_metrics']['profit'] > 0] if filteroptions['filter_min_trades'] > 0: trials = [ - x for x in trials - if x['results_metrics']['trade_count'] > filteroptions['filter_min_trades'] - ] + x for x in trials + if x['results_metrics']['trade_count'] > filteroptions['filter_min_trades'] + ] if filteroptions['filter_max_trades'] > 0: trials = [ - x for x in trials - if x['results_metrics']['trade_count'] < filteroptions['filter_max_trades'] - ] + x for x in trials + if x['results_metrics']['trade_count'] < filteroptions['filter_max_trades'] + ] if filteroptions['filter_min_avg_time'] is not None: trials = [x for x in trials if x['results_metrics']['trade_count'] > 0] trials = [ - x for x in trials - if x['results_metrics']['duration'] > filteroptions['filter_min_avg_time'] - ] + x for x in trials + if x['results_metrics']['duration'] > filteroptions['filter_min_avg_time'] + ] if filteroptions['filter_max_avg_time'] is not None: trials = [x for x in trials if x['results_metrics']['trade_count'] > 0] trials = [ - x for x in trials - if x['results_metrics']['duration'] < filteroptions['filter_max_avg_time'] - ] + x for x in trials + if x['results_metrics']['duration'] < filteroptions['filter_max_avg_time'] + ] if filteroptions['filter_min_avg_profit'] is not None: trials = [x for x in trials if x['results_metrics']['trade_count'] > 0] trials = [ - x for x in trials - if x['results_metrics']['avg_profit'] - > filteroptions['filter_min_avg_profit'] - ] + x for x in trials + if x['results_metrics']['avg_profit'] + > filteroptions['filter_min_avg_profit'] + ] if filteroptions['filter_max_avg_profit'] is not None: trials = [x for x in trials if x['results_metrics']['trade_count'] > 0] trials = [ - x for x in trials - if x['results_metrics']['avg_profit'] - < filteroptions['filter_max_avg_profit'] - ] + x for x in trials + if x['results_metrics']['avg_profit'] + < filteroptions['filter_max_avg_profit'] + ] if filteroptions['filter_min_total_profit'] is not None: trials = [x for x in trials if x['results_metrics']['trade_count'] > 0] trials = [ - x for x in trials - if x['results_metrics']['profit'] > filteroptions['filter_min_total_profit'] - ] + x for x in trials + if x['results_metrics']['profit'] > filteroptions['filter_min_total_profit'] + ] if filteroptions['filter_max_total_profit'] is not None: trials = [x for x in trials if x['results_metrics']['trade_count'] > 0] trials = [ - x for x in trials - if x['results_metrics']['profit'] < filteroptions['filter_max_total_profit'] - ] + x for x in trials + if x['results_metrics']['profit'] < filteroptions['filter_max_total_profit'] + ] logger.info(f"{len(trials)} " + ("best " if filteroptions['only_best'] else "") + From e204e3277bd277f3dbfcee58c764b559f105740b Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2020 08:37:58 +0000 Subject: [PATCH 082/202] Bump ccxt from 1.22.95 to 1.23.30 Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.22.95 to 1.23.30. - [Release notes](https://github.com/ccxt/ccxt/releases) - [Changelog](https://github.com/ccxt/ccxt/blob/master/CHANGELOG.md) - [Commits](https://github.com/ccxt/ccxt/compare/1.22.95...1.23.30) Signed-off-by: dependabot-preview[bot] --- requirements-common.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-common.txt b/requirements-common.txt index bdb1f1127..10d567a96 100644 --- a/requirements-common.txt +++ b/requirements-common.txt @@ -1,6 +1,6 @@ # requirements without requirements installable via conda # mainly used for Raspberry pi installs -ccxt==1.22.95 +ccxt==1.23.30 SQLAlchemy==1.3.13 python-telegram-bot==12.4.2 arrow==0.15.5 From 485075b8f2e8cd4a5f19456388d6b482b404b413 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2020 08:38:36 +0000 Subject: [PATCH 083/202] Bump scikit-learn from 0.22.1 to 0.22.2 Bumps [scikit-learn](https://github.com/scikit-learn/scikit-learn) from 0.22.1 to 0.22.2. - [Release notes](https://github.com/scikit-learn/scikit-learn/releases) - [Commits](https://github.com/scikit-learn/scikit-learn/compare/0.22.1...0.22.2) Signed-off-by: dependabot-preview[bot] --- requirements-hyperopt.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-hyperopt.txt b/requirements-hyperopt.txt index 2984229c1..c713317ec 100644 --- a/requirements-hyperopt.txt +++ b/requirements-hyperopt.txt @@ -3,7 +3,7 @@ # Required for hyperopt scipy==1.4.1 -scikit-learn==0.22.1 +scikit-learn==0.22.2 scikit-optimize==0.7.4 filelock==3.0.12 joblib==0.14.1 From a17f3fb8be7229fb064cb74f853a61b5428b3b0c Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2020 08:39:25 +0000 Subject: [PATCH 084/202] Bump plotly from 4.5.1 to 4.5.2 Bumps [plotly](https://github.com/plotly/plotly.py) from 4.5.1 to 4.5.2. - [Release notes](https://github.com/plotly/plotly.py/releases) - [Changelog](https://github.com/plotly/plotly.py/blob/master/CHANGELOG.md) - [Commits](https://github.com/plotly/plotly.py/compare/v4.5.1...v4.5.2) Signed-off-by: dependabot-preview[bot] --- requirements-plot.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-plot.txt b/requirements-plot.txt index 5e62a5e95..a70c3e0cf 100644 --- a/requirements-plot.txt +++ b/requirements-plot.txt @@ -1,5 +1,5 @@ # Include all requirements to run the bot. -r requirements.txt -plotly==4.5.1 +plotly==4.5.2 From 6e2290c4f0d142de4e924f7ee78b209d90ec3643 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 2 Mar 2020 20:05:54 +0100 Subject: [PATCH 085/202] Allow last to be empty - closes #3005 --- freqtrade/freqtradebot.py | 8 +++----- tests/test_freqtradebot.py | 8 ++++++++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 53493ad4c..04e3dd72f 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -257,12 +257,10 @@ class FreqtradeBot: else: logger.info(f"Using Last {bid_strategy['price_side'].capitalize()} / Last Price") ticker = self.exchange.fetch_ticker(pair) - rate = ticker[bid_strategy['price_side']] - if rate < ticker['last']: - ticker_rate = rate - else: + ticker_rate = ticker[bid_strategy['price_side']] + if ticker['last'] and ticker_rate > ticker['last']: balance = self.config['bid_strategy']['ask_last_balance'] - ticker_rate = rate + balance * (ticker['last'] - rate) + ticker_rate = ticker_rate + balance * (ticker['last'] - ticker_rate) used_rate = ticker_rate self._buy_rate_cache[pair] = used_rate diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 49000382f..a5506017c 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -914,6 +914,10 @@ def test_process_informative_pairs_added(default_conf, ticker, mocker) -> None: ('ask', 20, 19, 10, 0.3, 17), # Between ask and last ('ask', 5, 6, 10, 1.0, 5), # last bigger than ask ('ask', 5, 6, 10, 0.5, 5), # last bigger than ask + ('ask', 10, 20, None, 0.5, 10), # last not available - uses ask + ('ask', 4, 5, None, 0.5, 4), # last not available - uses ask + ('ask', 4, 5, None, 1, 4), # last not available - uses ask + ('ask', 4, 5, None, 0, 4), # last not available - uses ask ('bid', 10, 20, 10, 0.0, 20), # Full bid side ('bid', 10, 20, 10, 1.0, 10), # Full last side ('bid', 10, 20, 10, 0.5, 15), # Between bid and last @@ -921,6 +925,10 @@ def test_process_informative_pairs_added(default_conf, ticker, mocker) -> None: ('bid', 10, 20, 10, 0.3, 17), # Between bid and last ('bid', 4, 5, 10, 1.0, 5), # last bigger than bid ('bid', 4, 5, 10, 0.5, 5), # last bigger than bid + ('bid', 10, 20, None, 0.5, 20), # last not available - uses bid + ('bid', 4, 5, None, 0.5, 5), # last not available - uses bid + ('bid', 4, 5, None, 1, 5), # last not available - uses bid + ('bid', 4, 5, None, 0, 5), # last not available - uses bid ]) def test_get_buy_rate(mocker, default_conf, caplog, side, ask, bid, last, last_ab, expected) -> None: From 7713cfeb7996b478f20c65afab9a1b49318b062b Mon Sep 17 00:00:00 2001 From: Fredrik81 Date: Mon, 2 Mar 2020 21:02:32 +0100 Subject: [PATCH 086/202] Corrected logic for -j + and - argument --- freqtrade/optimize/hyperopt.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 91507c347..c500e71bf 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -558,7 +558,8 @@ class Hyperopt: cpus = cpu_count() logger.info(f"Found {cpus} CPU cores. Let's make them scream!") config_jobs = self.config.get('hyperopt_jobs', -1) - if self.total_epochs < cpus and (config_jobs > self.total_epochs or config_jobs < 0): + if (config_jobs < 0 and (cpus + config_jobs + 1) > self.total_epochs) \ + or (config_jobs > 0 and config_jobs > self.total_epochs): config_jobs = self.total_epochs logger.info(f'Number of parallel jobs set as: {config_jobs}') From 0e4862b0c8f8cc927675eb4e3fb098f479f1d0b9 Mon Sep 17 00:00:00 2001 From: Fredrik81 Date: Mon, 2 Mar 2020 22:58:54 +0100 Subject: [PATCH 087/202] Added logging if argument is miss-configured --- freqtrade/optimize/hyperopt.py | 1 + 1 file changed, 1 insertion(+) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index c500e71bf..98c19f15b 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -561,6 +561,7 @@ class Hyperopt: if (config_jobs < 0 and (cpus + config_jobs + 1) > self.total_epochs) \ or (config_jobs > 0 and config_jobs > self.total_epochs): config_jobs = self.total_epochs + logger.info("Job count invalid will correct") logger.info(f'Number of parallel jobs set as: {config_jobs}') self.dimensions: List[Dimension] = self.hyperopt_space() From 92425642da922740e319f9563c93a36873af946f Mon Sep 17 00:00:00 2001 From: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> Date: Tue, 3 Mar 2020 01:00:24 +0300 Subject: [PATCH 088/202] Fix config_jobs --- freqtrade/optimize/hyperopt.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 98c19f15b..66ce94ae0 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -558,10 +558,6 @@ class Hyperopt: cpus = cpu_count() logger.info(f"Found {cpus} CPU cores. Let's make them scream!") config_jobs = self.config.get('hyperopt_jobs', -1) - if (config_jobs < 0 and (cpus + config_jobs + 1) > self.total_epochs) \ - or (config_jobs > 0 and config_jobs > self.total_epochs): - config_jobs = self.total_epochs - logger.info("Job count invalid will correct") logger.info(f'Number of parallel jobs set as: {config_jobs}') self.dimensions: List[Dimension] = self.hyperopt_space() From a7d4755859740a62cc87dc7f168eb2f7fbb54f81 Mon Sep 17 00:00:00 2001 From: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> Date: Tue, 3 Mar 2020 01:20:14 +0300 Subject: [PATCH 089/202] optimize calculation of current_jobs --- freqtrade/optimize/hyperopt.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 66ce94ae0..27fb7bcdb 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -576,10 +576,12 @@ class Hyperopt: f_val = self.run_optimizer_parallel(parallel, asked, i) self.opt.tell(asked, [v['loss'] for v in f_val]) self.fix_optimizer_models_list() - if (i * jobs + jobs) > self.total_epochs: - current_jobs = jobs - ((i * jobs + jobs) - self.total_epochs) - else: - current_jobs = jobs + + # Correct the number of epochs to handled for the last + # iteration (should not exceed self.total_epochs) + n_rest = (i + 1) * jobs - self.total_epochs + current_jobs = jobs - n_rest if n_rest > 0 else jobs + for j in range(current_jobs): # Use human-friendly indexes here (starting from 1) current = i * jobs + j + 1 From 45c94967927609b556f3b5ec226cc5dc31d9a8a9 Mon Sep 17 00:00:00 2001 From: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> Date: Tue, 3 Mar 2020 01:33:11 +0300 Subject: [PATCH 090/202] Do not run optimizer for 'jobs' epochs for the last iteration --- freqtrade/optimize/hyperopt.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 27fb7bcdb..41e2ce82b 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -572,16 +572,16 @@ class Hyperopt: logger.info(f'Effective number of parallel workers used: {jobs}') EVALS = ceil(self.total_epochs / jobs) for i in range(EVALS): - asked = self.opt.ask(n_points=jobs) + # Correct the number of epochs to be processed for the last + # iteration (should not exceed self.total_epochs in total) + n_rest = (i + 1) * jobs - self.total_epochs + current_jobs = jobs - n_rest if n_rest > 0 else jobs + + asked = self.opt.ask(n_points=current_jobs) f_val = self.run_optimizer_parallel(parallel, asked, i) self.opt.tell(asked, [v['loss'] for v in f_val]) self.fix_optimizer_models_list() - # Correct the number of epochs to handled for the last - # iteration (should not exceed self.total_epochs) - n_rest = (i + 1) * jobs - self.total_epochs - current_jobs = jobs - n_rest if n_rest > 0 else jobs - for j in range(current_jobs): # Use human-friendly indexes here (starting from 1) current = i * jobs + j + 1 From 52cd5f912789d7b151702ba6c0a7caf3e4414846 Mon Sep 17 00:00:00 2001 From: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> Date: Tue, 3 Mar 2020 01:42:25 +0300 Subject: [PATCH 091/202] Better use enumerate: more correct and more pythonic --- freqtrade/optimize/hyperopt.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 41e2ce82b..b3be3f160 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -582,10 +582,9 @@ class Hyperopt: self.opt.tell(asked, [v['loss'] for v in f_val]) self.fix_optimizer_models_list() - for j in range(current_jobs): + for j, val in enumerate(f_val): # Use human-friendly indexes here (starting from 1) current = i * jobs + j + 1 - val = f_val[j] val['current_epoch'] = current val['is_initial_point'] = current <= INITIAL_POINTS logger.debug(f"Optimizer epoch evaluated: {val}") From 399c419163cd8acf867f16b9702b9936a1d05c7d Mon Sep 17 00:00:00 2001 From: Fredrik81 Date: Tue, 3 Mar 2020 01:14:56 +0100 Subject: [PATCH 092/202] Changed table formating. Adding some code to align hyperopt table generation. WIP --- freqtrade/optimize/hyperopt.py | 15 ++++++++++----- tests/optimize/test_hyperopt.py | 4 +++- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 700614453..ac272128e 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -325,15 +325,20 @@ class Hyperopt: trials['Trades'] = trials['Trades'].astype(str) trials['Epoch'] = trials['Epoch'].apply( - lambda x: "{}/{}".format(x, total_epochs)) + lambda x: '{}/{}'.format(str(x).rjust(len(str(total_epochs)), ' '), total_epochs)) trials['Avg profit'] = trials['Avg profit'].apply( - lambda x: '{:,.2f}%'.format(x) if not isna(x) else x) + lambda x: ('{:,.2f}%'.format(x)).rjust(7, ' ') if not isna(x) else "--".rjust(7, ' ')) trials['Profit'] = trials['Profit'].apply( - lambda x: '{:,.2f}%'.format(x) if not isna(x) else x) + lambda x: ('{:,.2f}%'.format(x)) if not isna(x) else "--") trials['Total profit'] = trials['Total profit'].apply( - lambda x: '{:,.8f} '.format(x) + config['stake_currency'] if not isna(x) else x) + lambda x: ('{:,.8f} '.format(x)) + config['stake_currency'] if not isna(x) else "--") trials['Avg duration'] = trials['Avg duration'].apply( - lambda x: '{:,.1f}m'.format(x) if not isna(x) else x) + lambda x: ('{:,.1f}m'.format(x)).rjust(7, ' ') if not isna(x) else "--".rjust(7, ' ')) + trials['Objective'] = trials['Objective'].apply( + lambda x: str(x).rjust(10, ' ') if str(x) != str(100000) else "N/A".rjust(10, ' ')) + trials['Profit'] = trials['Total profit'] + " (" + trials['Profit'] + ")" + trials = trials.drop(columns=['Total profit']) + if print_colorized: for i in range(len(trials)): if trials.loc[i]['is_profit']: diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index 5bd9e542f..8de812d2d 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -413,7 +413,9 @@ def test_log_results_if_loss_improves(hyperopt, capsys) -> None: '| Best | 2/2 | 1 | 0.10% | 0.00100000 BTC |' ' 1.00% | 20.0m | 1 |' ) - assert result_str in out + # assert result_str in out + assert all(x in out + for x in ["Best", "2/2", " 1", "0.10%", "0.00100000 BTC", "1.00%", "20.0m"]) def test_no_log_if_loss_does_not_improve(hyperopt, caplog) -> None: From 4aca8d7fcc2e217652572325aa72a61a70d8ec98 Mon Sep 17 00:00:00 2001 From: Fredrik81 Date: Tue, 3 Mar 2020 01:35:18 +0100 Subject: [PATCH 093/202] PEP8 fix --- tests/optimize/test_hyperopt.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index 8de812d2d..6c7e1e16f 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -409,11 +409,6 @@ def test_log_results_if_loss_improves(hyperopt, capsys) -> None: } ) out, err = capsys.readouterr() - result_str = ( - '| Best | 2/2 | 1 | 0.10% | 0.00100000 BTC |' - ' 1.00% | 20.0m | 1 |' - ) - # assert result_str in out assert all(x in out for x in ["Best", "2/2", " 1", "0.10%", "0.00100000 BTC", "1.00%", "20.0m"]) From 3479f7d986a044de4db94c6d1c9f3ac02b4544f6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 3 Mar 2020 07:13:11 +0100 Subject: [PATCH 094/202] Add max_drawdown function --- freqtrade/data/btanalysis.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index c28e462ba..9407a3139 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -3,7 +3,7 @@ Helpers when analyzing backtest data """ import logging from pathlib import Path -from typing import Dict, Union +from typing import Dict, Union, Tuple import numpy as np import pandas as pd @@ -188,3 +188,23 @@ def create_cum_profit(df: pd.DataFrame, trades: pd.DataFrame, col_name: str, # FFill to get continuous df[col_name] = df[col_name].ffill() return df + + +def calculate_max_drawdown(trades: pd.DataFrame) -> Tuple[float, pd.Timestamp, pd.Timestamp]: + """ + Calculate max drawdown and the corresponding close dates + :param trades: DataFrame containing trades (requires columns close_time and profitperc) + :return: Tuple (float, highdate, lowdate) with absolute max drawdown, high and low time + :raise: ValueError if trade-dataframe was found empty. + """ + if len(trades) == 0: + raise ValueError("Trade dataframe empty") + profit_results = trades.sort_values('close_time') + max_drawdown_df = pd.DataFrame() + max_drawdown_df['cumulative'] = profit_results['profitperc'].cumsum() + max_drawdown_df['high_value'] = max_drawdown_df['cumulative'].cummax() + max_drawdown_df['drawdown'] = max_drawdown_df['cumulative'] - max_drawdown_df['high_value'] + high_date = profit_results.loc[max_drawdown_df['high_value'].idxmax(), 'close_time'] + low_date = profit_results.loc[max_drawdown_df['drawdown'].idxmin(), 'close_time'] + + return abs(min(max_drawdown_df['drawdown'])), high_date, low_date From e050511ddcd295841590cfd99c4897e97dc678e3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 3 Mar 2020 07:20:41 +0100 Subject: [PATCH 095/202] Add test for max_drawdown calculation --- freqtrade/data/btanalysis.py | 2 +- tests/data/test_btanalysis.py | 20 ++++++++++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index 9407a3139..799f15011 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -198,7 +198,7 @@ def calculate_max_drawdown(trades: pd.DataFrame) -> Tuple[float, pd.Timestamp, p :raise: ValueError if trade-dataframe was found empty. """ if len(trades) == 0: - raise ValueError("Trade dataframe empty") + raise ValueError("Trade dataframe empty.") profit_results = trades.sort_values('close_time') max_drawdown_df = pd.DataFrame() max_drawdown_df['cumulative'] = profit_results['profitperc'].cumsum() diff --git a/tests/data/test_btanalysis.py b/tests/data/test_btanalysis.py index 60d9c3ea5..7e3c1f077 100644 --- a/tests/data/test_btanalysis.py +++ b/tests/data/test_btanalysis.py @@ -2,15 +2,17 @@ from unittest.mock import MagicMock import pytest from arrow import Arrow -from pandas import DataFrame, DateOffset, to_datetime +from pandas import DataFrame, DateOffset, to_datetime, Timestamp from freqtrade.configuration import TimeRange from freqtrade.data.btanalysis import (BT_DATA_COLUMNS, + analyze_trade_parallelism, + calculate_max_drawdown, combine_tickers_with_mean, create_cum_profit, extract_trades_of_period, load_backtest_data, load_trades, - load_trades_from_db, analyze_trade_parallelism) + load_trades_from_db) from freqtrade.data.history import load_data, load_pair_history from tests.test_persistence import create_mock_trades @@ -163,3 +165,17 @@ def test_create_cum_profit1(testdatadir): assert "cum_profits" in cum_profits.columns assert cum_profits.iloc[0]['cum_profits'] == 0 assert cum_profits.iloc[-1]['cum_profits'] == 0.0798005 + + +def test_calculate_max_drawdown(testdatadir): + filename = testdatadir / "backtest-result_test.json" + bt_data = load_backtest_data(filename) + drawdown, h, low = calculate_max_drawdown(bt_data) + assert isinstance(drawdown, float) + assert pytest.approx(drawdown) == 0.21142322 + assert isinstance(h, Timestamp) + assert isinstance(low, Timestamp) + assert h == Timestamp('2018-01-24 14:25:00', tz='UTC') + assert low == Timestamp('2018-01-30 04:45:00', tz='UTC') + with pytest.raises(ValueError, match='Trade dataframe empty.'): + drawdown, h, low = calculate_max_drawdown(DataFrame()) From 88e7cab5b99873d3f5af59ca61333013d8e2234b Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 3 Mar 2020 07:21:14 +0100 Subject: [PATCH 096/202] Add max_drawdown to profit plot --- freqtrade/plot/plotting.py | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/freqtrade/plot/plotting.py b/freqtrade/plot/plotting.py index 4a892792a..2ce4f1501 100644 --- a/freqtrade/plot/plotting.py +++ b/freqtrade/plot/plotting.py @@ -5,7 +5,8 @@ from typing import Any, Dict, List import pandas as pd from freqtrade.configuration import TimeRange -from freqtrade.data.btanalysis import (combine_tickers_with_mean, +from freqtrade.data.btanalysis import (calculate_max_drawdown, + combine_tickers_with_mean, create_cum_profit, extract_trades_of_period, load_trades) from freqtrade.data.converter import trim_dataframe @@ -111,6 +112,36 @@ def add_profit(fig, row, data: pd.DataFrame, column: str, name: str) -> make_sub return fig +def add_max_drawdown(fig, row, trades: pd.DataFrame, df_comb: pd.DataFrame) -> make_subplots: + """ + Add scatter points indicating max drawdown + """ + try: + max_drawdown, highdate, lowdate = calculate_max_drawdown(trades) + + drawdown = go.Scatter( + x=[highdate, lowdate], + y=[ + df_comb.loc[highdate, 'cum_profit'], + df_comb.loc[lowdate, 'cum_profit'], + ], + mode='markers', + name='Max Drawdown', + text=f"Max drawdown {max_drawdown}", + marker=dict( + symbol='square-open', + size=9, + line=dict(width=2), + color='green' + + ) + ) + fig.add_trace(drawdown, row, 1) + except ValueError: + logger.warning("No trades found - not plotting max drawdown.") + return fig + + def plot_trades(fig, trades: pd.DataFrame) -> make_subplots: """ Add trades to "fig" @@ -364,6 +395,7 @@ def generate_profit_graph(pairs: str, tickers: Dict[str, pd.DataFrame], fig.add_trace(avgclose, 1, 1) fig = add_profit(fig, 2, df_comb, 'cum_profit', 'Profit') + fig = add_max_drawdown(fig, 2, trades, df_comb) for pair in pairs: profit_col = f'cum_profit_{pair}' From 33a63562cbacdeb858a29b09c4a11b9d3f31ae7b Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 3 Mar 2020 07:23:38 +0100 Subject: [PATCH 097/202] make drawdown function less restrictive --- freqtrade/data/btanalysis.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index 799f15011..394c40112 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -190,21 +190,26 @@ def create_cum_profit(df: pd.DataFrame, trades: pd.DataFrame, col_name: str, return df -def calculate_max_drawdown(trades: pd.DataFrame) -> Tuple[float, pd.Timestamp, pd.Timestamp]: +def calculate_max_drawdown(trades: pd.DataFrame, date_col: str = 'close_time', + value_col: str = 'profitperc' + ) -> Tuple[float, pd.Timestamp, pd.Timestamp]: """ Calculate max drawdown and the corresponding close dates :param trades: DataFrame containing trades (requires columns close_time and profitperc) + :param date_col: Column in DataFrame to use for dates (defaults to 'close_time') + :param value_col: Column in DataFrame to use for values (defaults to 'profitperc') :return: Tuple (float, highdate, lowdate) with absolute max drawdown, high and low time :raise: ValueError if trade-dataframe was found empty. """ if len(trades) == 0: raise ValueError("Trade dataframe empty.") - profit_results = trades.sort_values('close_time') + profit_results = trades.sort_values(date_col) max_drawdown_df = pd.DataFrame() - max_drawdown_df['cumulative'] = profit_results['profitperc'].cumsum() + max_drawdown_df['cumulative'] = profit_results[value_col].cumsum() max_drawdown_df['high_value'] = max_drawdown_df['cumulative'].cummax() max_drawdown_df['drawdown'] = max_drawdown_df['cumulative'] - max_drawdown_df['high_value'] - high_date = profit_results.loc[max_drawdown_df['high_value'].idxmax(), 'close_time'] - low_date = profit_results.loc[max_drawdown_df['drawdown'].idxmin(), 'close_time'] + + high_date = profit_results.loc[max_drawdown_df['high_value'].idxmax(), date_col] + low_date = profit_results.loc[max_drawdown_df['drawdown'].idxmin(), date_col] return abs(min(max_drawdown_df['drawdown'])), high_date, low_date From d9e83cc4e2892bb9210c166dbd0857fc7b9e87b9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 3 Mar 2020 09:33:08 +0100 Subject: [PATCH 098/202] Run CI on windows python 3.8 --- .github/workflows/ci.yml | 5 ++--- .../TA_Lib-0.4.17-cp38-cp38-win_amd64.whl | Bin 0 -> 662134 bytes build_helpers/install_windows.ps1 | 10 +++++++++- 3 files changed, 11 insertions(+), 4 deletions(-) create mode 100644 build_helpers/TA_Lib-0.4.17-cp38-cp38-win_amd64.whl diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dc3d324a1..42668e46f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -115,7 +115,7 @@ jobs: strategy: matrix: os: [ windows-latest ] - python-version: [3.7] + python-version: [3.7, 3.8] steps: - uses: actions/checkout@v2 @@ -130,8 +130,7 @@ jobs: if: startsWith(runner.os, 'Windows') with: path: ~\AppData\Local\pip\Cache - key: ${{ runner.os }}-pip - restore-keys: ${{ runner.os }}-pip + key: ${{ matrix.os }}-${{ matrix.python-version }}-pip - name: Installation run: | diff --git a/build_helpers/TA_Lib-0.4.17-cp38-cp38-win_amd64.whl b/build_helpers/TA_Lib-0.4.17-cp38-cp38-win_amd64.whl new file mode 100644 index 0000000000000000000000000000000000000000..90626b183a42e55d12ddabf57d3d257aea29fbf5 GIT binary patch literal 662134 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

O_G_btw< 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 c-u=Va$x49#GO+I{BZxql@8(eq1LnQ Date: Tue, 3 Mar 2020 09:36:04 +0100 Subject: [PATCH 099/202] Remove flucky test assert --- tests/commands/test_commands.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index 1877aaa43..3e1c0a581 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -448,9 +448,6 @@ def test_create_datadir_failed(caplog): def test_create_datadir(caplog, mocker): - # Capture caplog length here trying to avoid random test failure - len_caplog_before = len(caplog.record_tuples) - cud = mocker.patch("freqtrade.commands.deploy_commands.create_userdata_dir", MagicMock()) csf = mocker.patch("freqtrade.commands.deploy_commands.copy_sample_files", MagicMock()) args = [ @@ -462,7 +459,6 @@ def test_create_datadir(caplog, mocker): assert cud.call_count == 1 assert csf.call_count == 1 - assert len(caplog.record_tuples) == len_caplog_before def test_start_new_strategy(mocker, caplog): From 9bebc9ba7699426a86271f711f98e080142531a8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 3 Mar 2020 09:40:41 +0100 Subject: [PATCH 100/202] Fix powershell comparison --- build_helpers/install_windows.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build_helpers/install_windows.ps1 b/build_helpers/install_windows.ps1 index af1768d18..7dbdd77dd 100644 --- a/build_helpers/install_windows.ps1 +++ b/build_helpers/install_windows.ps1 @@ -6,10 +6,10 @@ python -m pip install --upgrade pip $pyv = python -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')" -if ($pyv == '3.7') { +if ($pyv -eq '3.7') { pip install build_helpers\TA_Lib-0.4.17-cp37-cp37m-win_amd64.whl } -if ($pyv == '3.8') { +if ($pyv -eq '3.8') { pip install build_helpers\TA_Lib-0.4.17-cp38-cp38-win_amd64.whl } From 53dcb5d5ed1dd1dc741e9cf3f6d0b49f90e55029 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 3 Mar 2020 19:36:03 +0100 Subject: [PATCH 101/202] Fix logging expression --- freqtrade/freqtradebot.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 04e3dd72f..920c9203e 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -685,7 +685,8 @@ class FreqtradeBot: order_book_max=order_book_max) for i in range(order_book_min, order_book_max + 1): sell_rate = next(order_book) - logger.debug(' order book asks top %s: %0.8f', i, sell_rate) + logger.debug(f" order book {config_ask_strategy['price_side']} top {i}: " + f"{sell_rate:0.8f}") if self._check_and_execute_sell(trade, sell_rate, buy, sell): return True From 9d8970a76bdff307482bdf82dffe2279af3fa731 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 3 Mar 2020 20:18:38 +0100 Subject: [PATCH 102/202] Add test and formatting to drawdown --- docs/plotting.md | 1 + freqtrade/data/btanalysis.py | 2 +- freqtrade/plot/plotting.py | 4 ++-- tests/test_plotting.py | 8 ++++++-- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/docs/plotting.md b/docs/plotting.md index ecd5e1603..3eef8f8e7 100644 --- a/docs/plotting.md +++ b/docs/plotting.md @@ -196,6 +196,7 @@ The first graph is good to get a grip of how the overall market progresses. The second graph will show if your algorithm works or doesn't. Perhaps you want an algorithm that steadily makes small profits, or one that acts less often, but makes big swings. +This graph will also highlight the start (and end) of the Max drawdown period. The third graph can be useful to spot outliers, events in pairs that cause profit spikes. diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index 394c40112..7972c6333 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -190,7 +190,7 @@ def create_cum_profit(df: pd.DataFrame, trades: pd.DataFrame, col_name: str, return df -def calculate_max_drawdown(trades: pd.DataFrame, date_col: str = 'close_time', +def calculate_max_drawdown(trades: pd.DataFrame, *, date_col: str = 'close_time', value_col: str = 'profitperc' ) -> Tuple[float, pd.Timestamp, pd.Timestamp]: """ diff --git a/freqtrade/plot/plotting.py b/freqtrade/plot/plotting.py index 2ce4f1501..d979a40e0 100644 --- a/freqtrade/plot/plotting.py +++ b/freqtrade/plot/plotting.py @@ -126,8 +126,8 @@ def add_max_drawdown(fig, row, trades: pd.DataFrame, df_comb: pd.DataFrame) -> m df_comb.loc[lowdate, 'cum_profit'], ], mode='markers', - name='Max Drawdown', - text=f"Max drawdown {max_drawdown}", + name=f"Max drawdown {max_drawdown:.2f}%", + text=f"Max drawdown {max_drawdown:.2f}%", marker=dict( symbol='square-open', size=9, diff --git a/tests/test_plotting.py b/tests/test_plotting.py index 34d1f2b0c..dd04035b7 100644 --- a/tests/test_plotting.py +++ b/tests/test_plotting.py @@ -3,15 +3,16 @@ from copy import deepcopy from pathlib import Path from unittest.mock import MagicMock +import pandas as pd import plotly.graph_objects as go import pytest from plotly.subplots import make_subplots +from freqtrade.commands import start_plot_dataframe, start_plot_profit from freqtrade.configuration import TimeRange from freqtrade.data import history from freqtrade.data.btanalysis import create_cum_profit, load_backtest_data from freqtrade.exceptions import OperationalException -from freqtrade.commands import start_plot_dataframe, start_plot_profit from freqtrade.plot.plotting import (add_indicators, add_profit, create_plotconfig, generate_candlestick_graph, @@ -266,6 +267,7 @@ def test_generate_profit_graph(testdatadir): trades = load_backtest_data(filename) timerange = TimeRange.parse_timerange("20180110-20180112") pairs = ["TRX/BTC", "ADA/BTC"] + trades = trades[trades['close_time'] < pd.Timestamp('2018-01-12', tz='UTC')] tickers = history.load_data(datadir=testdatadir, pairs=pairs, @@ -283,13 +285,15 @@ def test_generate_profit_graph(testdatadir): assert fig.layout.yaxis3.title.text == "Profit" figure = fig.layout.figure - assert len(figure.data) == 4 + assert len(figure.data) == 5 avgclose = find_trace_in_fig_data(figure.data, "Avg close price") assert isinstance(avgclose, go.Scatter) profit = find_trace_in_fig_data(figure.data, "Profit") assert isinstance(profit, go.Scatter) + profit = find_trace_in_fig_data(figure.data, "Max drawdown 0.00%") + assert isinstance(profit, go.Scatter) for pair in pairs: profit_pair = find_trace_in_fig_data(figure.data, f"Profit {pair}") From 7652a2bb95451ad907aec48ff4a8cc133901ae40 Mon Sep 17 00:00:00 2001 From: Fredrik81 Date: Wed, 4 Mar 2020 00:10:47 +0100 Subject: [PATCH 103/202] Updated table layout and aligning better for hyperopt --- freqtrade/optimize/hyperopt.py | 50 ++++++++++++++++++++------------- tests/optimize/test_hyperopt.py | 2 +- 2 files changed, 31 insertions(+), 21 deletions(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index ac272128e..7937ad611 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -21,7 +21,7 @@ from colorama import init as colorama_init from joblib import (Parallel, cpu_count, delayed, dump, load, wrap_non_picklable_objects) from pandas import DataFrame, json_normalize, isna -from tabulate import tabulate +import tabulate from freqtrade.data.converter import trim_dataframe from freqtrade.data.history import get_timerange @@ -309,6 +309,8 @@ class Hyperopt: if not results: return + tabulate.PRESERVE_WHITESPACE = True + trials = json_normalize(results, max_level=1) trials['Best'] = '' trials = trials[['Best', 'current_epoch', 'results_metrics.trade_count', @@ -325,43 +327,51 @@ class Hyperopt: trials['Trades'] = trials['Trades'].astype(str) trials['Epoch'] = trials['Epoch'].apply( - lambda x: '{}/{}'.format(str(x).rjust(len(str(total_epochs)), ' '), total_epochs)) + lambda x: '{}/{}'.format(str(x).rjust(len(str(total_epochs)), ' '), total_epochs) + ) trials['Avg profit'] = trials['Avg profit'].apply( - lambda x: ('{:,.2f}%'.format(x)).rjust(7, ' ') if not isna(x) else "--".rjust(7, ' ')) - trials['Profit'] = trials['Profit'].apply( - lambda x: ('{:,.2f}%'.format(x)) if not isna(x) else "--") - trials['Total profit'] = trials['Total profit'].apply( - lambda x: ('{:,.8f} '.format(x)) + config['stake_currency'] if not isna(x) else "--") + lambda x: ('{:,.2f}%'.format(x)).rjust(7, ' ') if not isna(x) else "--".rjust(7, ' ') + ) trials['Avg duration'] = trials['Avg duration'].apply( - lambda x: ('{:,.1f}m'.format(x)).rjust(7, ' ') if not isna(x) else "--".rjust(7, ' ')) + lambda x: ('{:,.1f} m'.format(x)).rjust(7, ' ') if not isna(x) else "--".rjust(7, ' ') + ) trials['Objective'] = trials['Objective'].apply( - lambda x: str(x).rjust(10, ' ') if str(x) != str(100000) else "N/A".rjust(10, ' ')) - trials['Profit'] = trials['Total profit'] + " (" + trials['Profit'] + ")" + lambda x: str(x).rjust(10, ' ') if str(x) != str(100000) else "N/A".rjust(10, ' ') + ) + + trials['Profit'] = trials.apply( + lambda x: '{:,.8f} {} ({:,.2f}%)'.format( + x['Total profit'], config['stake_currency'], + x['Profit']).rjust(24+len(config['stake_currency'])) + if x['Total profit'] != 0.0 else '--'.rjust(24+len(config['stake_currency'])), + axis=1 + ) trials = trials.drop(columns=['Total profit']) if print_colorized: for i in range(len(trials)): if trials.loc[i]['is_profit']: for z in range(len(trials.loc[i])-3): - trials.iat[i, z] = "{}{}{}".format(Fore.GREEN, - str(trials.loc[i][z]), Fore.RESET) + trials.iat[i, z] = "{}{}{}".format( + Fore.GREEN, str(trials.loc[i][z]), Fore.RESET) if trials.loc[i]['is_best'] and highlight_best: for z in range(len(trials.loc[i])-3): - trials.iat[i, z] = "{}{}{}".format(Style.BRIGHT, - str(trials.loc[i][z]), Style.RESET_ALL) + trials.iat[i, z] = "{}{}{}".format( + Style.BRIGHT, str(trials.loc[i][z]), Style.RESET_ALL) trials = trials.drop(columns=['is_initial_point', 'is_best', 'is_profit']) if remove_header > 0: - table = tabulate(trials.to_dict(orient='list'), tablefmt='orgtbl', - headers='keys', stralign="right") + table = tabulate.tabulate( + trials.to_dict(orient='list'), tablefmt='orgtbl', headers='keys', stralign="right") + # print(table) table = table.split("\n", remove_header)[remove_header] elif remove_header < 0: - table = tabulate(trials.to_dict(orient='list'), tablefmt='psql', - headers='keys', stralign="right") + table = tabulate.tabulate( + trials.to_dict(orient='list'), tablefmt='psql', headers='keys', stralign="right") table = "\n".join(table.split("\n")[0:remove_header]) else: - table = tabulate(trials.to_dict(orient='list'), tablefmt='psql', - headers='keys', stralign="right") + table = tabulate.tabulate( + trials.to_dict(orient='list'), tablefmt='psql', headers='keys', stralign="right") print(table) def has_space(self, space: str) -> bool: diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index 6c7e1e16f..5820cc9f9 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -410,7 +410,7 @@ def test_log_results_if_loss_improves(hyperopt, capsys) -> None: ) out, err = capsys.readouterr() assert all(x in out - for x in ["Best", "2/2", " 1", "0.10%", "0.00100000 BTC", "1.00%", "20.0m"]) + for x in ["Best", "2/2", " 1", "0.10%", "0.00100000 BTC (1.00%)", "20.0 m"]) def test_no_log_if_loss_does_not_improve(hyperopt, caplog) -> None: From 8de35e1c83becae0f226deb1f55fd7277510cf41 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 4 Mar 2020 06:40:19 +0100 Subject: [PATCH 104/202] Documentation suggestions from Review Co-Authored-By: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> --- docs/configuration.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index c70f3425c..c2c144541 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -465,7 +465,7 @@ Orderbook `bid` (buy) side depth is then divided by the orderbook `ask` (sell) s #### Buy price side -The configuration option `bid_strategy.price_side` defines the side of the spread the bot looks for when buying. +The configuration setting `bid_strategy.price_side` defines the side of the spread the bot looks for when buying. The following displays an orderbook. @@ -503,7 +503,7 @@ The `bid_strategy.ask_last_balance` configuration parameter controls this. A val #### Sell price side -The configuration option `ask_strategy.price_side` defines the side of the spread the bot looks for when selling. +The configuration setting `ask_strategy.price_side` defines the side of the spread the bot looks for when selling. The following displays an orderbook: @@ -524,7 +524,7 @@ In line with that, if `ask_strategy.price_side` is set to `"bid"`, then the bot #### Sell price with Orderbook enabled -When selling with the orderbook enabled (`ask_strategy.use_order_book=True`), Freqtrade fetches the `ask_strategy.order_book_max` entries in the orderbook. Then each of the orderbook steps between `ask_strategy.order_book_min` and `ask_strategy.order_book_max` on the configured orderbook side are validated for a profitable sell-possibility based on the strategy configuration and the sell order is placed at the first profitable spot. +When selling with the orderbook enabled (`ask_strategy.use_order_book=True`), Freqtrade fetches the `ask_strategy.order_book_max` entries in the orderbook. Then each of the orderbook steps between `ask_strategy.order_book_min` and `ask_strategy.order_book_max` on the configured orderbook side are validated for a profitable sell-possibility based on the strategy configuration (`minimal_roi` conditions) and the sell order is placed at the first profitable spot. !!! Note Using `order_book_max` higher than `order_book_min` only makes sense when ask_strategy.price_side is set to `"ask"`. From 2f54aff0ce25d04edf25f9663c62c9138cabf622 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 4 Mar 2020 06:44:47 +0100 Subject: [PATCH 105/202] Improve documentation wording for price sides --- docs/configuration.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index c2c144541..7e33f1528 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -485,7 +485,8 @@ If `bid_strategy.price_side` is set to `"bid"`, then the bot will use 99 as buyi In line with that, if `bid_strategy.price_side` is set to `"ask"`, then the bot will use 101 as buying price. Using `ask` price often guarantees quicker filled orders, but the bot can also end up paying more than what would have been necessary. - +Taker fees instead of maker fees will most likely apply even when using limit buy orders. +Also, prices at the "ask" side of the spread are higher than prices at the "bid" side in the orderbook, so the order behaves similar to a market order (however with a maximum price). #### Buy price with Orderbook enabled From 090d1e8a709d9c0abcbd1cff667207f4e22d25dd Mon Sep 17 00:00:00 2001 From: Fredrik81 Date: Wed, 4 Mar 2020 20:51:09 +0100 Subject: [PATCH 106/202] Alignment and cleanups --- freqtrade/optimize/hyperopt.py | 38 +++++++++++++++++++-------------- tests/optimize/test_hyperopt.py | 4 +--- 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 7937ad611..bc0a0d58f 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -154,7 +154,7 @@ class Hyperopt: """ num_trials = len(self.trials) if num_trials > self.num_trials_saved: - logger.info(f"Saving {num_trials} {plural(num_trials, 'epoch')}.") + logger.debug(f"Saving {num_trials} {plural(num_trials, 'epoch')}.") dump(self.trials, self.trials_file) self.num_trials_saved = num_trials if final: @@ -322,7 +322,6 @@ class Hyperopt: trials['is_profit'] = False trials.loc[trials['is_initial_point'], 'Best'] = '*' trials.loc[trials['is_best'], 'Best'] = 'Best' - trials['Objective'] = trials['Objective'].astype(str) trials.loc[trials['Total profit'] > 0, 'is_profit'] = True trials['Trades'] = trials['Trades'].astype(str) @@ -336,14 +335,15 @@ class Hyperopt: lambda x: ('{:,.1f} m'.format(x)).rjust(7, ' ') if not isna(x) else "--".rjust(7, ' ') ) trials['Objective'] = trials['Objective'].apply( - lambda x: str(x).rjust(10, ' ') if str(x) != str(100000) else "N/A".rjust(10, ' ') + lambda x: '{:,.5f}'.format(x).rjust(8, ' ') if x != 100000 else "N/A".rjust(8, ' ') ) trials['Profit'] = trials.apply( - lambda x: '{:,.8f} {} ({:,.2f}%)'.format( + lambda x: '{:,.8f} {} {}'.format( x['Total profit'], config['stake_currency'], - x['Profit']).rjust(24+len(config['stake_currency'])) - if x['Total profit'] != 0.0 else '--'.rjust(24+len(config['stake_currency'])), + '({:,.2f}%)'.format(x['Profit']).rjust(10, ' ') + ).rjust(25+len(config['stake_currency'])) + if x['Total profit'] != 0.0 else '--'.rjust(25+len(config['stake_currency'])), axis=1 ) trials = trials.drop(columns=['Total profit']) @@ -351,27 +351,33 @@ class Hyperopt: if print_colorized: for i in range(len(trials)): if trials.loc[i]['is_profit']: - for z in range(len(trials.loc[i])-3): - trials.iat[i, z] = "{}{}{}".format( - Fore.GREEN, str(trials.loc[i][z]), Fore.RESET) + for j in range(len(trials.loc[i])-3): + trials.iat[i, j] = "{}{}{}".format(Fore.GREEN, + str(trials.loc[i][j]), Fore.RESET) if trials.loc[i]['is_best'] and highlight_best: - for z in range(len(trials.loc[i])-3): - trials.iat[i, z] = "{}{}{}".format( - Style.BRIGHT, str(trials.loc[i][z]), Style.RESET_ALL) + for j in range(len(trials.loc[i])-3): + trials.iat[i, j] = "{}{}{}".format(Style.BRIGHT, + str(trials.loc[i][j]), Style.RESET_ALL) trials = trials.drop(columns=['is_initial_point', 'is_best', 'is_profit']) if remove_header > 0: table = tabulate.tabulate( - trials.to_dict(orient='list'), tablefmt='orgtbl', headers='keys', stralign="right") - # print(table) + trials.to_dict(orient='list'), tablefmt='orgtbl', + headers='keys', stralign="right" + ) + table = table.split("\n", remove_header)[remove_header] elif remove_header < 0: table = tabulate.tabulate( - trials.to_dict(orient='list'), tablefmt='psql', headers='keys', stralign="right") + trials.to_dict(orient='list'), tablefmt='psql', + headers='keys', stralign="right" + ) table = "\n".join(table.split("\n")[0:remove_header]) else: table = tabulate.tabulate( - trials.to_dict(orient='list'), tablefmt='psql', headers='keys', stralign="right") + trials.to_dict(orient='list'), tablefmt='psql', + headers='keys', stralign="right" + ) print(table) def has_space(self, space: str) -> bool: diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index 5820cc9f9..0e35ff827 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -410,7 +410,7 @@ def test_log_results_if_loss_improves(hyperopt, capsys) -> None: ) out, err = capsys.readouterr() assert all(x in out - for x in ["Best", "2/2", " 1", "0.10%", "0.00100000 BTC (1.00%)", "20.0 m"]) + for x in ["Best", "2/2", " 1", "0.10%", "0.00100000 BTC (1.00%)", "20.0 m"]) def test_no_log_if_loss_does_not_improve(hyperopt, caplog) -> None: @@ -432,13 +432,11 @@ def test_save_trials_saves_trials(mocker, hyperopt, testdatadir, caplog) -> None hyperopt.trials = trials hyperopt.save_trials(final=True) - assert log_has("Saving 1 epoch.", caplog) assert log_has(f"1 epoch saved to '{trials_file}'.", caplog) mock_dump.assert_called_once() hyperopt.trials = trials + trials hyperopt.save_trials(final=True) - assert log_has("Saving 2 epochs.", caplog) assert log_has(f"2 epochs saved to '{trials_file}'.", caplog) From 7606d814fa3158f7383ecca47e60ebb9c8be9595 Mon Sep 17 00:00:00 2001 From: Fredrik81 Date: Thu, 5 Mar 2020 01:58:33 +0100 Subject: [PATCH 107/202] Initial work on csv-file export. Missing docs and tests --- freqtrade/commands/arguments.py | 3 +- freqtrade/commands/cli_options.py | 6 +++ freqtrade/commands/hyperopt_commands.py | 12 +++++ freqtrade/configuration/configuration.py | 3 ++ freqtrade/optimize/hyperopt.py | 58 ++++++++++++++++++++++++ 5 files changed, 81 insertions(+), 1 deletion(-) diff --git a/freqtrade/commands/arguments.py b/freqtrade/commands/arguments.py index 73e77d69d..8a8b06782 100644 --- a/freqtrade/commands/arguments.py +++ b/freqtrade/commands/arguments.py @@ -69,7 +69,8 @@ ARGS_HYPEROPT_LIST = ["hyperopt_list_best", "hyperopt_list_profitable", "hyperopt_list_min_avg_time", "hyperopt_list_max_avg_time", "hyperopt_list_min_avg_profit", "hyperopt_list_max_avg_profit", "hyperopt_list_min_total_profit", "hyperopt_list_max_total_profit", - "print_colorized", "print_json", "hyperopt_list_no_details"] + "print_colorized", "print_json", "hyperopt_list_no_details", + "export_csv"] ARGS_HYPEROPT_SHOW = ["hyperopt_list_best", "hyperopt_list_profitable", "hyperopt_show_index", "print_json", "hyperopt_show_no_header"] diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py index ef674c5c2..77fba8eef 100644 --- a/freqtrade/commands/cli_options.py +++ b/freqtrade/commands/cli_options.py @@ -221,6 +221,12 @@ AVAILABLE_CLI_OPTIONS = { action='store_true', default=False, ), + "export_csv": Arg( + '--export-csv', + help='Export to CSV-File. Put + in front of filename to overwrite.' + 'Example: --export-csv +hyperopt.csv', + metavar='FILE', + ), "hyperopt_jobs": Arg( '-j', '--job-workers', help='The number of concurrently running jobs for hyperoptimization ' diff --git a/freqtrade/commands/hyperopt_commands.py b/freqtrade/commands/hyperopt_commands.py index 4803f6885..f4f119351 100755 --- a/freqtrade/commands/hyperopt_commands.py +++ b/freqtrade/commands/hyperopt_commands.py @@ -21,6 +21,7 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None: print_colorized = config.get('print_colorized', False) print_json = config.get('print_json', False) + export_csv = config.get('export_csv', None) no_details = config.get('hyperopt_list_no_details', False) no_header = False @@ -59,6 +60,17 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None: sorted_trials = sorted(trials, key=itemgetter('loss')) results = sorted_trials[0] Hyperopt.print_epoch_details(results, total_epochs, print_json, no_header) + print(export_csv) + if trials and export_csv: + overwrite_csv = False + if export_csv[0] == '+': + overwrite_csv = True + export_csv = export_csv[1:] + + Hyperopt.export_csv_file( + config, trials, total_epochs, + not filteroptions['only_best'], export_csv, overwrite_csv + ) def start_hyperopt_show(args: Dict[str, Any]) -> None: diff --git a/freqtrade/configuration/configuration.py b/freqtrade/configuration/configuration.py index 6a0441957..0645d72be 100644 --- a/freqtrade/configuration/configuration.py +++ b/freqtrade/configuration/configuration.py @@ -282,6 +282,9 @@ class Configuration: self._args_to_config(config, argname='print_json', logstring='Parameter --print-json detected ...') + self._args_to_config(config, argname='export_csv', + logstring='Parameter --export-csv detected: {}') + self._args_to_config(config, argname='hyperopt_jobs', logstring='Parameter -j/--job-workers detected: {}') diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index e9ab469f4..d397c720c 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -23,6 +23,8 @@ from joblib import (Parallel, cpu_count, delayed, dump, load, wrap_non_picklable_objects) from pandas import DataFrame, json_normalize, isna import tabulate +from os import path +import io from freqtrade.data.converter import trim_dataframe from freqtrade.data.history import get_timerange @@ -381,6 +383,62 @@ class Hyperopt: ) print(table) + @staticmethod + def export_csv_file(config: dict, results: list, total_epochs: int, highlight_best: bool, + csv_file: str, overwrite: bool) -> None: + """ + Log result to csv-file + """ + if not results: + return + + # Verification for owerwrite + if not overwrite and path.isfile(csv_file): + logging.error("CSV-File already exists and no overwrite specified!") + return + + try: + io.open(csv_file, 'w+').close() + except IOError: + logging.error("Filed to create/overwrite CSV-File!") + return + + trials = json_normalize(results, max_level=1) + trials['Best'] = '' + trials['Stake currency'] = config['stake_currency'] + trials = trials[['Best', 'current_epoch', 'results_metrics.trade_count', + 'results_metrics.avg_profit', 'results_metrics.total_profit', + 'Stake currency', 'results_metrics.profit', 'results_metrics.duration', + 'loss', 'is_initial_point', 'is_best']] + trials.columns = ['Best', 'Epoch', 'Trades', 'Avg profit', 'Total profit', 'Stake currency', + 'Profit', 'Avg duration', 'Objective', 'is_initial_point', 'is_best'] + trials['is_profit'] = False + trials.loc[trials['is_initial_point'], 'Best'] = '*' + trials.loc[trials['is_best'], 'Best'] = 'Best' + trials.loc[trials['Total profit'] > 0, 'is_profit'] = True + trials['Epoch'] = trials['Epoch'].astype(str) + trials['Trades'] = trials['Trades'].astype(str) + + trials['Total profit'] = trials['Total profit'].apply( + lambda x: '{:,.8f}'.format(x) if x != 0.0 else "--" + ) + trials['Profit'] = trials['Profit'].apply( + lambda x: '{:,.2f}'.format(x) if not isna(x) else "--" + ) + trials['Avg profit'] = trials['Avg profit'].apply( + lambda x: ('{:,.2f}%'.format(x)) if not isna(x) else "--" + ) + trials['Avg duration'] = trials['Avg duration'].apply( + lambda x: ('{:,.1f} m'.format(x)) if not isna(x) else "--" + ) + trials['Objective'] = trials['Objective'].apply( + lambda x: '{:,.5f}'.format(x) if x != 100000 else "N/A" + ) + + trials = trials.drop(columns=['is_initial_point', 'is_best', 'is_profit']) + trials.to_csv(csv_file, index=False, header=True, mode='w', encoding='UTF-8') + print("CSV-File created!") + def has_space(self, space: str) -> bool: """ Tell if the space value is contained in the configuration From 97b194a45485310199b76abadba46446f31ab42f Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 5 Mar 2020 06:20:36 +0100 Subject: [PATCH 108/202] Throttle may take longer than .10s on slow machines This made this test fluky on windows CI ... --- tests/test_worker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_worker.py b/tests/test_worker.py index 7b446ac6a..839f7cdac 100644 --- a/tests/test_worker.py +++ b/tests/test_worker.py @@ -60,7 +60,7 @@ def test_throttle(mocker, default_conf, caplog) -> None: assert result == 42 assert end - start > 0.1 - assert log_has_re(r"Throttling with 'throttled_func\(\)': sleep for 0\.10 s.*", caplog) + assert log_has_re(r"Throttling with 'throttled_func\(\)': sleep for \d\.\d{2} s.*", caplog) result = worker._throttle(throttled_func, throttle_secs=-1) assert result == 42 From 7a3660cd6b0abca9dc42017f0c2303c602e05a67 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Thu, 5 Mar 2020 17:44:21 +0300 Subject: [PATCH 109/202] Adjust webhook tests --- tests/rpc/test_rpc_webhook.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/rpc/test_rpc_webhook.py b/tests/rpc/test_rpc_webhook.py index 3f3f36766..1ced62746 100644 --- a/tests/rpc/test_rpc_webhook.py +++ b/tests/rpc/test_rpc_webhook.py @@ -28,12 +28,12 @@ def get_webhook_dict() -> dict: "webhooksell": { "value1": "Selling {pair}", "value2": "limit {limit:8f}", - "value3": "profit: {profit_amount:8f} {stake_currency}" + "value3": "profit: {profit_amount:8f} {stake_currency} ({profit_ratio})" }, "webhooksellcancel": { "value1": "Cancelling Open Sell Order for {pair}", "value2": "limit {limit:8f}", - "value3": "profit: {profit_amount:8f} {stake_currency}" + "value3": "profit: {profit_amount:8f} {stake_currency} ({profit_ratio})" }, "webhookstatus": { "value1": "Status: {status}", @@ -110,7 +110,7 @@ def test_send_msg(default_conf, mocker): 'open_rate': 0.004, 'current_rate': 0.005, 'profit_amount': 0.001, - 'profit_percent': 0.20, + 'profit_ratio': 0.20, 'stake_currency': 'BTC', 'sell_reason': SellType.STOP_LOSS.value } @@ -136,7 +136,7 @@ def test_send_msg(default_conf, mocker): 'open_rate': 0.004, 'current_rate': 0.005, 'profit_amount': 0.001, - 'profit_percent': 0.20, + 'profit_ratio': 0.20, 'stake_currency': 'BTC', 'sell_reason': SellType.STOP_LOSS.value } From eee5727426fd0001ec8ddfdbf529dac435d503c4 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Thu, 5 Mar 2020 17:44:38 +0300 Subject: [PATCH 110/202] Adjust webhook docs --- docs/webhook-config.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/webhook-config.md b/docs/webhook-config.md index e53aa8af5..70a41dd46 100644 --- a/docs/webhook-config.md +++ b/docs/webhook-config.md @@ -23,12 +23,12 @@ Sample configuration (tested using IFTTT). "webhooksell": { "value1": "Selling {pair}", "value2": "limit {limit:8f}", - "value3": "profit: {profit_amount:8f} {stake_currency}" + "value3": "profit: {profit_amount:8f} {stake_currency} ({profit_ratio})" }, "webhooksellcancel": { "value1": "Cancelling Open Sell Order for {pair}", "value2": "limit {limit:8f}", - "value3": "profit: {profit_amount:8f} {stake_currency}" + "value3": "profit: {profit_amount:8f} {stake_currency} ({profit_ratio})" }, "webhookstatus": { "value1": "Status: {status}", @@ -87,7 +87,7 @@ Possible parameters are: * `open_rate` * `current_rate` * `profit_amount` -* `profit_percent` +* `profit_ratio` * `stake_currency` * `fiat_currency` * `sell_reason` @@ -108,7 +108,7 @@ Possible parameters are: * `open_rate` * `current_rate` * `profit_amount` -* `profit_percent` +* `profit_ratio` * `stake_currency` * `fiat_currency` * `sell_reason` From 91db75a7073763d8c00c1057b5485d50e5103e46 Mon Sep 17 00:00:00 2001 From: Fredrik81 Date: Thu, 5 Mar 2020 19:43:43 +0100 Subject: [PATCH 111/202] Added tests and updated doc --- docs/utils.md | 3 +++ freqtrade/commands/cli_options.py | 3 ++- freqtrade/commands/hyperopt_commands.py | 13 +++++++------ tests/commands/test_commands.py | 16 +++++++++++++++- 4 files changed, 27 insertions(+), 8 deletions(-) diff --git a/docs/utils.md b/docs/utils.md index cdf0c31af..dd7a9dfe3 100644 --- a/docs/utils.md +++ b/docs/utils.md @@ -450,6 +450,9 @@ optional arguments: useful if you are redirecting output to a file. --print-json Print best result detailization in JSON format. --no-details Do not print best epoch details. + --export-csv FILE Export to CSV-File. Put + in front of filename to + overwrite. This will disable table print. Example: + --export-csv +hyperopt.csv Common arguments: -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py index 77fba8eef..b782c2fb9 100644 --- a/freqtrade/commands/cli_options.py +++ b/freqtrade/commands/cli_options.py @@ -224,7 +224,8 @@ AVAILABLE_CLI_OPTIONS = { "export_csv": Arg( '--export-csv', help='Export to CSV-File. Put + in front of filename to overwrite.' - 'Example: --export-csv +hyperopt.csv', + ' This will disable table print.' + ' Example: --export-csv +hyperopt.csv', metavar='FILE', ), "hyperopt_jobs": Arg( diff --git a/freqtrade/commands/hyperopt_commands.py b/freqtrade/commands/hyperopt_commands.py index f4f119351..efc9aba88 100755 --- a/freqtrade/commands/hyperopt_commands.py +++ b/freqtrade/commands/hyperopt_commands.py @@ -50,17 +50,18 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None: if print_colorized: colorama_init(autoreset=True) - try: - Hyperopt.print_result_table(config, trials, total_epochs, - not filteroptions['only_best'], print_colorized, 0) - except KeyboardInterrupt: - print('User interrupted..') + if not export_csv: + try: + Hyperopt.print_result_table(config, trials, total_epochs, + not filteroptions['only_best'], print_colorized, 0) + except KeyboardInterrupt: + print('User interrupted..') if trials and not no_details: sorted_trials = sorted(trials, key=itemgetter('loss')) results = sorted_trials[0] Hyperopt.print_epoch_details(results, total_epochs, print_json, no_header) - print(export_csv) + if trials and export_csv: overwrite_csv = False if export_csv[0] == '+': diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index 3e1c0a581..58e97fd49 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -902,7 +902,21 @@ def test_hyperopt_list(mocker, capsys, hyperopt_results): assert all(x not in captured.out for x in [" 1/12", " 3/12", " 4/12", " 5/12", " 7/12", " 8/12" " 9/12", " 10/12", " 11/12", " 12/12"]) - + args = [ + "hyperopt-list", + "--no-details", + "--export-csv", "+test_file.csv" + ] + pargs = get_args(args) + pargs['config'] = None + start_hyperopt_list(pargs) + captured = capsys.readouterr() + assert all(x in captured.out + for x in ["CSV-File created!"]) + f = Path("test_file.csv") + assert 'Best,1,2,-1.25%,-0.00125625,,-2.51,"3,930.0 m",0.43662' in f.read_text() + assert f.is_file() + f.unlink() def test_hyperopt_show(mocker, capsys, hyperopt_results): mocker.patch( From f0d56e23a340e77dac0c6cd64ea3de2eeaedc1c4 Mon Sep 17 00:00:00 2001 From: Fredrik81 Date: Thu, 5 Mar 2020 19:58:01 +0100 Subject: [PATCH 112/202] PEP8 fix --- tests/commands/test_commands.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index 58e97fd49..f404a4671 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -918,6 +918,7 @@ def test_hyperopt_list(mocker, capsys, hyperopt_results): assert f.is_file() f.unlink() + def test_hyperopt_show(mocker, capsys, hyperopt_results): mocker.patch( 'freqtrade.optimize.hyperopt.Hyperopt.load_previous_results', From 4474482307cb280bd3119dc7a34b96cb3582339b Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Thu, 5 Mar 2020 20:44:29 +0100 Subject: [PATCH 113/202] unifying get_sell_rate with get_buy_rate --- freqtrade/freqtradebot.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 4cbacdb1e..c800e4f7f 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -246,7 +246,7 @@ class FreqtradeBot: if 'use_order_book' in bid_strategy and bid_strategy.get('use_order_book', False): logger.info( f"Getting price from order book {bid_strategy['price_side'].capitalize()} side." - ) + ) order_book_top = bid_strategy.get('order_book_top', 1) order_book = self.exchange.get_order_book(pair, order_book_top) logger.debug('order_book %s', order_book) @@ -643,14 +643,16 @@ class FreqtradeBot: logger.info(f"Using cached sell rate for {pair}.") return rate - config_ask_strategy = self.config.get('ask_strategy', {}) - if config_ask_strategy.get('use_order_book', False): + ask_strategy = self.config.get('ask_strategy', {}) + if ask_strategy.get('use_order_book', False): # This code is only used for notifications, selling uses the generator directly - logger.debug('Using order book to get sell rate') - rate = next(self._order_book_gen(pair, f"{config_ask_strategy['price_side']}s")) + logger.info( + f"Getting price from order book {ask_strategy['price_side'].capitalize()} side." + ) + rate = next(self._order_book_gen(pair, f"{ask_strategy['price_side']}s")) else: - rate = self.exchange.fetch_ticker(pair)[config_ask_strategy['price_side']] + rate = self.exchange.fetch_ticker(pair)[ask_strategy['price_side']] self._sell_rate_cache[pair] = rate return rate From 0587256733a1f9df8deb4ac4c6f63a2ded10e274 Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Thu, 5 Mar 2020 21:57:01 +0100 Subject: [PATCH 114/202] minor create_trade() optimization --- freqtrade/freqtradebot.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 4cbacdb1e..e5ae9043a 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -246,7 +246,7 @@ class FreqtradeBot: if 'use_order_book' in bid_strategy and bid_strategy.get('use_order_book', False): logger.info( f"Getting price from order book {bid_strategy['price_side'].capitalize()} side." - ) + ) order_book_top = bid_strategy.get('order_book_top', 1) order_book = self.exchange.get_order_book(pair, order_book_top) logger.debug('order_book %s', order_book) @@ -394,21 +394,21 @@ class FreqtradeBot: logger.info(f"Pair {pair} is currently locked.") return False + if not self.get_free_open_trades(): + logger.debug(f"Can't open a new trade for {pair}: max number of trades is reached.") + return False + + stake_amount = self.get_trade_stake_amount(pair) + if not stake_amount: + logger.debug(f"Stake amount is 0, ignoring possible trade for {pair}.") + return False + # running get_signal on historical data fetched (buy, sell) = self.strategy.get_signal( pair, self.strategy.ticker_interval, self.dataprovider.ohlcv(pair, self.strategy.ticker_interval)) if buy and not sell: - if not self.get_free_open_trades(): - logger.debug("Can't open a new trade: max number of trades is reached.") - return False - - stake_amount = self.get_trade_stake_amount(pair) - if not stake_amount: - logger.debug(f"Stake amount is 0, ignoring possible trade for {pair}.") - return False - logger.info(f"Buy signal found: about create a new trade with stake_amount: " f"{stake_amount} ...") From b8d05d87511c6f29971a1d422b7d57eef9cda5a5 Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Thu, 5 Mar 2020 22:14:05 +0100 Subject: [PATCH 115/202] found instance of config get without default --- freqtrade/freqtradebot.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 4cbacdb1e..4d4ad14fc 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -246,7 +246,7 @@ class FreqtradeBot: if 'use_order_book' in bid_strategy and bid_strategy.get('use_order_book', False): logger.info( f"Getting price from order book {bid_strategy['price_side'].capitalize()} side." - ) + ) order_book_top = bid_strategy.get('order_book_top', 1) order_book = self.exchange.get_order_book(pair, order_book_top) logger.debug('order_book %s', order_book) @@ -669,7 +669,7 @@ class FreqtradeBot: config_ask_strategy = self.config.get('ask_strategy', {}) if (config_ask_strategy.get('use_sell_signal', True) or - config_ask_strategy.get('ignore_roi_if_buy_signal')): + config_ask_strategy.get('ignore_roi_if_buy_signal', False)): (buy, sell) = self.strategy.get_signal( trade.pair, self.strategy.ticker_interval, self.dataprovider.ohlcv(trade.pair, self.strategy.ticker_interval)) From 78908e24965405a13755f62cf25659d26c55cd51 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 6 Mar 2020 15:47:16 +0100 Subject: [PATCH 116/202] Fix travis build failure --- .travis.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index ec688a1f4..0cb76b78b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,3 @@ -sudo: true os: - linux dist: xenial @@ -11,10 +10,10 @@ env: global: - IMAGE_NAME=freqtradeorg/freqtrade install: -- cd build_helpers && ./install_ta-lib.sh ${HOME}/dependencies/; cd .. +- cd build_helpers && ./install_ta-lib.sh ${HOME}/dependencies; cd .. - export LD_LIBRARY_PATH=${HOME}/dependencies/lib:$LD_LIBRARY_PATH - export TA_LIBRARY_PATH=${HOME}/dependencies/lib -- export TA_INCLUDE_PATH=${HOME}/dependencies/lib/include +- export TA_INCLUDE_PATH=${HOME}/dependencies/include - pip install -r requirements-dev.txt - pip install -e . jobs: From 93fc14d72653caae85bc7ae5139418c0ac0b3b3a Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 7 Mar 2020 11:52:02 +0100 Subject: [PATCH 117/202] Exchange dependencies to coingekko --- requirements-common.txt | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements-common.txt b/requirements-common.txt index 10d567a96..a844e81bc 100644 --- a/requirements-common.txt +++ b/requirements-common.txt @@ -11,7 +11,7 @@ wrapt==1.12.0 jsonschema==3.2.0 TA-Lib==0.4.17 tabulate==0.8.6 -coinmarketcap==5.0.3 +pycoingecko==1.2.0 jinja2==2.11.1 # find first, C search in arrays diff --git a/setup.py b/setup.py index 63a595f32..7890f862e 100644 --- a/setup.py +++ b/setup.py @@ -73,7 +73,7 @@ setup(name='freqtrade', 'jsonschema', 'TA-Lib', 'tabulate', - 'coinmarketcap', + 'pycoingecko', 'py_find_1st', 'python-rapidjson', 'sdnotify', From df5adb6ca59358aa2df47039b66deb89f900dda0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 7 Mar 2020 11:52:26 +0100 Subject: [PATCH 118/202] Exchange coingekko for coinmarketcap --- freqtrade/rpc/fiat_convert.py | 52 ++++++++++++++++---------------- tests/conftest.py | 24 +++++++-------- tests/rpc/test_fiat_convert.py | 54 +++++++++++++++++----------------- 3 files changed, 65 insertions(+), 65 deletions(-) diff --git a/freqtrade/rpc/fiat_convert.py b/freqtrade/rpc/fiat_convert.py index d40f9221e..99d06dde0 100644 --- a/freqtrade/rpc/fiat_convert.py +++ b/freqtrade/rpc/fiat_convert.py @@ -7,7 +7,7 @@ import logging import time from typing import Dict, List -from coinmarketcap import Market +from pycoingecko import CoinGeckoAPI from freqtrade.constants import SUPPORTED_FIAT @@ -38,8 +38,8 @@ class CryptoFiat: # Private attributes self._expiration = 0.0 - self.crypto_symbol = crypto_symbol.upper() - self.fiat_symbol = fiat_symbol.upper() + self.crypto_symbol = crypto_symbol.lower() + self.fiat_symbol = fiat_symbol.lower() self.set_price(price=price) def set_price(self, price: float) -> None: @@ -67,17 +67,20 @@ class CryptoToFiatConverter: This object is also a Singleton """ __instance = None - _coinmarketcap: Market = None + _coingekko: CoinGeckoAPI = None _cryptomap: Dict = {} def __new__(cls): + """ + This class is a singleton - should not be instanciated twice. + """ if CryptoToFiatConverter.__instance is None: CryptoToFiatConverter.__instance = object.__new__(cls) try: - CryptoToFiatConverter._coinmarketcap = Market() + CryptoToFiatConverter._coingekko = CoinGeckoAPI() except BaseException: - CryptoToFiatConverter._coinmarketcap = None + CryptoToFiatConverter._coingekko = None return CryptoToFiatConverter.__instance def __init__(self) -> None: @@ -86,14 +89,12 @@ class CryptoToFiatConverter: def _load_cryptomap(self) -> None: try: - coinlistings = self._coinmarketcap.listings() - self._cryptomap = dict(map(lambda coin: (coin["symbol"], str(coin["id"])), - coinlistings["data"])) - except (BaseException) as exception: + coinlistings = self._coingekko.get_coins_list() + # Create mapping table from synbol to coingekko_id + self._cryptomap = {x['symbol']: x['id'] for x in coinlistings} + except (Exception) as exception: logger.error( - "Could not load FIAT Cryptocurrency map for the following problem: %s", - type(exception).__name__ - ) + f"Could not load FIAT Cryptocurrency map for the following problem: {exception}") def convert_amount(self, crypto_amount: float, crypto_symbol: str, fiat_symbol: str) -> float: """ @@ -115,8 +116,8 @@ class CryptoToFiatConverter: :param fiat_symbol: FIAT currency you want to convert to (e.g USD) :return: Price in FIAT """ - crypto_symbol = crypto_symbol.upper() - fiat_symbol = fiat_symbol.upper() + crypto_symbol = crypto_symbol.lower() + fiat_symbol = fiat_symbol.lower() # Check if the fiat convertion you want is supported if not self._is_supported_fiat(fiat=fiat_symbol): @@ -170,15 +171,13 @@ class CryptoToFiatConverter: :return: bool, True supported, False not supported """ - fiat = fiat.upper() - - return fiat in SUPPORTED_FIAT + return fiat.upper() in SUPPORTED_FIAT def _find_price(self, crypto_symbol: str, fiat_symbol: str) -> float: """ - Call CoinMarketCap API to retrieve the price in the FIAT - :param crypto_symbol: Crypto-currency you want to convert (e.g BTC) - :param fiat_symbol: FIAT currency you want to convert to (e.g USD) + Call CoinGekko API to retrieve the price in the FIAT + :param crypto_symbol: Crypto-currency you want to convert (e.g btc) + :param fiat_symbol: FIAT currency you want to convert to (e.g usd) :return: float, price of the crypto-currency in Fiat """ # Check if the fiat convertion you want is supported @@ -195,12 +194,13 @@ class CryptoToFiatConverter: return 0.0 try: + _gekko_id = self._cryptomap[crypto_symbol] return float( - self._coinmarketcap.ticker( - currency=self._cryptomap[crypto_symbol], - convert=fiat_symbol - )['data']['quotes'][fiat_symbol.upper()]['price'] + self._coingekko.get_price( + ids=_gekko_id, + vs_currencies=fiat_symbol + )[_gekko_id][fiat_symbol] ) - except BaseException as exception: + except Exception as exception: logger.error("Error in _find_price: %s", exception) return 0.0 diff --git a/tests/conftest.py b/tests/conftest.py index 000f62868..e8e3fe9e3 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -167,23 +167,23 @@ def patch_get_signal(freqtrade: FreqtradeBot, value=(True, False)) -> None: @pytest.fixture(autouse=True) -def patch_coinmarketcap(mocker) -> None: +def patch_coingekko(mocker) -> None: """ - Mocker to coinmarketcap to speed up tests - :param mocker: mocker to patch coinmarketcap class + Mocker to coingekko to speed up tests + :param mocker: mocker to patch coingekko class :return: None """ - tickermock = MagicMock(return_value={'price_usd': 12345.0}) - listmock = MagicMock(return_value={'data': [{'id': 1, 'name': 'Bitcoin', 'symbol': 'BTC', - 'website_slug': 'bitcoin'}, - {'id': 1027, 'name': 'Ethereum', 'symbol': 'ETH', - 'website_slug': 'ethereum'} - ]}) + tickermock = MagicMock(return_value={'bitcoin': {'usd': 12345.0}, 'ethereum': {'usd': 12345.0}}) + listmock = MagicMock(return_value=[{'id': 'bitcoin', 'name': 'Bitcoin', 'symbol': 'btc', + 'website_slug': 'bitcoin'}, + {'id': 'ethereum', 'name': 'Ethereum', 'symbol': 'eth', + 'website_slug': 'ethereum'} + ]) mocker.patch.multiple( - 'freqtrade.rpc.fiat_convert.Market', - ticker=tickermock, - listings=listmock, + 'freqtrade.rpc.fiat_convert.CoinGeckoAPI', + get_price=tickermock, + get_coins_list=listmock, ) diff --git a/tests/rpc/test_fiat_convert.py b/tests/rpc/test_fiat_convert.py index 05760ce25..ed21bc516 100644 --- a/tests/rpc/test_fiat_convert.py +++ b/tests/rpc/test_fiat_convert.py @@ -8,7 +8,7 @@ import pytest from requests.exceptions import RequestException from freqtrade.rpc.fiat_convert import CryptoFiat, CryptoToFiatConverter -from tests.conftest import log_has +from tests.conftest import log_has, log_has_re def test_pair_convertion_object(): @@ -22,8 +22,8 @@ def test_pair_convertion_object(): assert pair_convertion.CACHE_DURATION == 6 * 60 * 60 # Check a regular usage - assert pair_convertion.crypto_symbol == 'BTC' - assert pair_convertion.fiat_symbol == 'USD' + assert pair_convertion.crypto_symbol == 'btc' + assert pair_convertion.fiat_symbol == 'usd' assert pair_convertion.price == 12345.0 assert pair_convertion.is_expired() is False @@ -57,15 +57,15 @@ def test_fiat_convert_add_pair(mocker): fiat_convert._add_pair(crypto_symbol='btc', fiat_symbol='usd', price=12345.0) pair_len = len(fiat_convert._pairs) assert pair_len == 1 - assert fiat_convert._pairs[0].crypto_symbol == 'BTC' - assert fiat_convert._pairs[0].fiat_symbol == 'USD' + assert fiat_convert._pairs[0].crypto_symbol == 'btc' + assert fiat_convert._pairs[0].fiat_symbol == 'usd' assert fiat_convert._pairs[0].price == 12345.0 fiat_convert._add_pair(crypto_symbol='btc', fiat_symbol='Eur', price=13000.2) pair_len = len(fiat_convert._pairs) assert pair_len == 2 - assert fiat_convert._pairs[1].crypto_symbol == 'BTC' - assert fiat_convert._pairs[1].fiat_symbol == 'EUR' + assert fiat_convert._pairs[1].crypto_symbol == 'btc' + assert fiat_convert._pairs[1].fiat_symbol == 'eur' assert fiat_convert._pairs[1].price == 13000.2 @@ -100,15 +100,15 @@ def test_fiat_convert_get_price(mocker): fiat_convert = CryptoToFiatConverter() - with pytest.raises(ValueError, match=r'The fiat US DOLLAR is not supported.'): - fiat_convert.get_price(crypto_symbol='BTC', fiat_symbol='US Dollar') + with pytest.raises(ValueError, match=r'The fiat us dollar is not supported.'): + fiat_convert.get_price(crypto_symbol='btc', fiat_symbol='US Dollar') # Check the value return by the method pair_len = len(fiat_convert._pairs) assert pair_len == 0 - assert fiat_convert.get_price(crypto_symbol='BTC', fiat_symbol='USD') == 28000.0 - assert fiat_convert._pairs[0].crypto_symbol == 'BTC' - assert fiat_convert._pairs[0].fiat_symbol == 'USD' + assert fiat_convert.get_price(crypto_symbol='btc', fiat_symbol='usd') == 28000.0 + assert fiat_convert._pairs[0].crypto_symbol == 'btc' + assert fiat_convert._pairs[0].fiat_symbol == 'usd' assert fiat_convert._pairs[0].price == 28000.0 assert fiat_convert._pairs[0]._expiration != 0 assert len(fiat_convert._pairs) == 1 @@ -116,13 +116,13 @@ def test_fiat_convert_get_price(mocker): # Verify the cached is used fiat_convert._pairs[0].price = 9867.543 expiration = fiat_convert._pairs[0]._expiration - assert fiat_convert.get_price(crypto_symbol='BTC', fiat_symbol='USD') == 9867.543 + assert fiat_convert.get_price(crypto_symbol='btc', fiat_symbol='usd') == 9867.543 assert fiat_convert._pairs[0]._expiration == expiration # Verify the cache expiration expiration = time.time() - 2 * 60 * 60 fiat_convert._pairs[0]._expiration = expiration - assert fiat_convert.get_price(crypto_symbol='BTC', fiat_symbol='USD') == 28000.0 + assert fiat_convert.get_price(crypto_symbol='btc', fiat_symbol='usd') == 28000.0 assert fiat_convert._pairs[0]._expiration is not expiration @@ -143,15 +143,15 @@ def test_loadcryptomap(mocker): fiat_convert = CryptoToFiatConverter() assert len(fiat_convert._cryptomap) == 2 - assert fiat_convert._cryptomap["BTC"] == "1" + assert fiat_convert._cryptomap["btc"] == "bitcoin" def test_fiat_init_network_exception(mocker): # Because CryptoToFiatConverter is a Singleton we reset the listings listmock = MagicMock(side_effect=RequestException) mocker.patch.multiple( - 'freqtrade.rpc.fiat_convert.Market', - listings=listmock, + 'freqtrade.rpc.fiat_convert.CoinGeckoAPI', + get_coins_list=listmock, ) # with pytest.raises(RequestEsxception): fiat_convert = CryptoToFiatConverter() @@ -163,24 +163,24 @@ def test_fiat_init_network_exception(mocker): def test_fiat_convert_without_network(mocker): - # Because CryptoToFiatConverter is a Singleton we reset the value of _coinmarketcap + # Because CryptoToFiatConverter is a Singleton we reset the value of _coingekko fiat_convert = CryptoToFiatConverter() - cmc_temp = CryptoToFiatConverter._coinmarketcap - CryptoToFiatConverter._coinmarketcap = None + cmc_temp = CryptoToFiatConverter._coingekko + CryptoToFiatConverter._coingekko = None - assert fiat_convert._coinmarketcap is None - assert fiat_convert._find_price(crypto_symbol='BTC', fiat_symbol='USD') == 0.0 - CryptoToFiatConverter._coinmarketcap = cmc_temp + assert fiat_convert._coingekko is None + assert fiat_convert._find_price(crypto_symbol='btc', fiat_symbol='usd') == 0.0 + CryptoToFiatConverter._coingekko = cmc_temp def test_fiat_invalid_response(mocker, caplog): # Because CryptoToFiatConverter is a Singleton we reset the listings listmock = MagicMock(return_value="{'novalidjson':DEADBEEFf}") mocker.patch.multiple( - 'freqtrade.rpc.fiat_convert.Market', - listings=listmock, + 'freqtrade.rpc.fiat_convert.CoinGeckoAPI', + get_coins_list=listmock, ) # with pytest.raises(RequestEsxception): fiat_convert = CryptoToFiatConverter() @@ -189,8 +189,8 @@ def test_fiat_invalid_response(mocker, caplog): length_cryptomap = len(fiat_convert._cryptomap) assert length_cryptomap == 0 - assert log_has('Could not load FIAT Cryptocurrency map for the following problem: TypeError', - caplog) + assert log_has_re('Could not load FIAT Cryptocurrency map for the following problem: .*', + caplog) def test_convert_amount(mocker): From acea49beaf364bc9890a4d08e0897fe45fd8ff0e Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 7 Mar 2020 13:01:26 +0100 Subject: [PATCH 119/202] Fix tests / test mocks --- tests/rpc/test_rpc.py | 22 +++++++++++----------- tests/rpc/test_rpc_telegram.py | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 6319ab9e6..47ffb771b 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -95,8 +95,8 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None: mocker.patch.multiple( - 'freqtrade.rpc.fiat_convert.Market', - ticker=MagicMock(return_value={'price_usd': 15000.0}), + 'freqtrade.rpc.fiat_convert.CoinGeckoAPI', + get_price=MagicMock(return_value={'bitcoin': {'usd': 15000.0}}), ) mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) @@ -178,7 +178,7 @@ def test_rpc_daily_profit(default_conf, update, ticker, fee, day[1] == '0.00006217 BTC') assert (day[2] == '0.000 USD' or - day[2] == '0.933 USD') + day[2] == '0.767 USD') # ensure first day is current date assert str(days[0][0]) == str(datetime.utcnow().date()) @@ -190,8 +190,8 @@ def test_rpc_daily_profit(default_conf, update, ticker, fee, def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee, limit_buy_order, limit_sell_order, mocker) -> None: mocker.patch.multiple( - 'freqtrade.rpc.fiat_convert.Market', - ticker=MagicMock(return_value={'price_usd': 15000.0}), + 'freqtrade.rpc.fiat_convert.CoinGeckoAPI', + get_price=MagicMock(return_value={'bitcoin': {'usd': 15000.0}}), ) mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) @@ -273,8 +273,8 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee, def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, fee, ticker_sell_up, limit_buy_order, limit_sell_order): mocker.patch.multiple( - 'freqtrade.rpc.fiat_convert.Market', - ticker=MagicMock(return_value={'price_usd': 15000.0}), + 'freqtrade.rpc.fiat_convert.CoinGeckoAPI', + get_price=MagicMock(return_value={'bitcoin': {'usd': 15000.0}}), ) mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0) @@ -341,8 +341,8 @@ def test_rpc_balance_handle_error(default_conf, mocker): # ETH will be skipped due to mocked Error below mocker.patch.multiple( - 'freqtrade.rpc.fiat_convert.Market', - ticker=MagicMock(return_value={'price_usd': 15000.0}), + 'freqtrade.rpc.fiat_convert.CoinGeckoAPI', + get_price=MagicMock(return_value={'bitcoin': {'usd': 15000.0}}), ) mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) @@ -380,8 +380,8 @@ def test_rpc_balance_handle(default_conf, mocker, tickers): } mocker.patch.multiple( - 'freqtrade.rpc.fiat_convert.Market', - ticker=MagicMock(return_value={'price_usd': 15000.0}), + 'freqtrade.rpc.fiat_convert.CoinGeckoAPI', + get_price=MagicMock(return_value={'bitcoin': {'usd': 15000.0}}), ) mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index cea863ac8..d769016c4 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -1210,7 +1210,7 @@ def test_send_msg_buy_notification(default_conf, mocker) -> None: '*Amount:* `1333.33333333`\n' \ '*Open Rate:* `0.00001099`\n' \ '*Current Rate:* `0.00001099`\n' \ - '*Total:* `(0.001000 BTC, 0.000 USD)`' + '*Total:* `(0.001000 BTC, 12.345 USD)`' def test_send_msg_buy_cancel_notification(default_conf, mocker) -> None: From 1b3038390a5298e3774047e5aac46e6d6d9711bb Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 7 Mar 2020 13:05:46 +0100 Subject: [PATCH 120/202] Update comment --- freqtrade/rpc/fiat_convert.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/rpc/fiat_convert.py b/freqtrade/rpc/fiat_convert.py index 99d06dde0..4e26432d4 100644 --- a/freqtrade/rpc/fiat_convert.py +++ b/freqtrade/rpc/fiat_convert.py @@ -73,7 +73,7 @@ class CryptoToFiatConverter: def __new__(cls): """ - This class is a singleton - should not be instanciated twice. + This class is a singleton - cannot be instantiated twice. """ if CryptoToFiatConverter.__instance is None: CryptoToFiatConverter.__instance = object.__new__(cls) From d51bb9acfb27d090272f9a2b67fbd61cc3d3f898 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 7 Mar 2020 13:11:36 +0100 Subject: [PATCH 121/202] Update conda environment file --- environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/environment.yml b/environment.yml index 4e8c1efcc..86ea03519 100644 --- a/environment.yml +++ b/environment.yml @@ -45,7 +45,7 @@ dependencies: - pip: # Required for app - cython - - coinmarketcap + - pycoingecko - ccxt - TA-Lib - py_find_1st From 281cf577d1b63601265361c739fc04b1f404fa37 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 7 Mar 2020 17:03:31 +0100 Subject: [PATCH 122/202] Remove unsupported FIAT --- freqtrade/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index ac1a8a6a9..54f620631 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -43,7 +43,7 @@ SUPPORTED_FIAT = [ "EUR", "GBP", "HKD", "HUF", "IDR", "ILS", "INR", "JPY", "KRW", "MXN", "MYR", "NOK", "NZD", "PHP", "PKR", "PLN", "RUB", "SEK", "SGD", "THB", "TRY", "TWD", "ZAR", "USD", - "BTC", "XBT", "ETH", "XRP", "LTC", "BCH", "USDT" + "BTC", "ETH", "XRP", "LTC", "BCH" ] MINIMAL_CONFIG = { From 3208faf7ede6be0898951b8d0bffd2966071f1b7 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Sun, 8 Mar 2020 13:35:31 +0300 Subject: [PATCH 123/202] Do not use ticker where it's not a ticker --- docs/backtesting.md | 12 +-- docs/configuration.md | 8 +- docs/data-download.md | 12 +-- docs/developer.md | 6 +- docs/edge.md | 2 +- docs/exchanges.md | 4 +- docs/hyperopt.md | 17 ++-- docs/strategy-customization.md | 17 ++-- docs/utils.md | 6 +- freqtrade/commands/arguments.py | 4 +- freqtrade/commands/build_config_commands.py | 2 +- freqtrade/commands/cli_options.py | 2 +- freqtrade/configuration/timerange.py | 2 +- freqtrade/data/btanalysis.py | 10 +- freqtrade/data/converter.py | 55 +++++------ freqtrade/data/dataprovider.py | 14 +-- freqtrade/data/history/history_utils.py | 22 ++--- freqtrade/data/history/idatahandler.py | 25 +++-- freqtrade/data/history/jsondatahandler.py | 4 +- freqtrade/edge/edge_positioning.py | 16 +-- freqtrade/exchange/exchange.py | 79 +++++++-------- freqtrade/freqtradebot.py | 8 +- freqtrade/misc.py | 12 +-- freqtrade/optimize/backtesting.py | 48 ++++----- freqtrade/optimize/hyperopt.py | 12 +-- freqtrade/plot/plotting.py | 24 ++--- freqtrade/strategy/interface.py | 30 +++--- freqtrade/templates/base_strategy.py.j2 | 2 +- freqtrade/templates/sample_strategy.py | 2 +- tests/conftest.py | 23 ++--- tests/data/test_btanalysis.py | 11 +-- tests/data/test_converter.py | 28 +++--- tests/data/test_dataprovider.py | 36 +++---- tests/data/test_history.py | 48 ++++----- tests/edge/test_edge.py | 26 ++--- tests/exchange/test_exchange.py | 102 ++++++++++---------- tests/optimize/__init__.py | 10 +- tests/optimize/test_backtesting.py | 44 ++++----- tests/optimize/test_hyperopt.py | 44 ++++----- tests/strategy/strats/default_strategy.py | 2 +- tests/strategy/test_interface.py | 52 +++++----- tests/test_misc.py | 8 +- tests/test_plotting.py | 20 ++-- 43 files changed, 459 insertions(+), 452 deletions(-) diff --git a/docs/backtesting.md b/docs/backtesting.md index 79bfa2350..3d08d5332 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -11,8 +11,8 @@ Now you have good Buy and Sell strategies and some historic data, you want to te real data. This is what we call [backtesting](https://en.wikipedia.org/wiki/Backtesting). -Backtesting will use the crypto-currencies (pairs) from your config file and load ticker data from `user_data/data/` by default. -If no data is available for the exchange / pair / ticker interval combination, backtesting will ask you to download them first using `freqtrade download-data`. +Backtesting will use the crypto-currencies (pairs) from your config file and load historical candle (OHCLV) data from `user_data/data/` by default. +If no data is available for the exchange / pair / timeframe (ticker interval) combination, backtesting will ask you to download them first using `freqtrade download-data`. For details on downloading, please refer to the [Data Downloading](data-download.md) section in the documentation. The result of backtesting will confirm if your bot has better odds of making a profit than a loss. @@ -22,19 +22,19 @@ The result of backtesting will confirm if your bot has better odds of making a p ### Run a backtesting against the currencies listed in your config file -#### With 5 min tickers (Per default) +#### With 5 min candle (OHLCV) data (per default) ```bash freqtrade backtesting ``` -#### With 1 min tickers +#### With 1 min candle (OHLCV) data ```bash freqtrade backtesting --ticker-interval 1m ``` -#### Using a different on-disk ticker-data source +#### Using a different on-disk historical candle (OHLCV) data source Assume you downloaded the history data from the Bittrex exchange and kept it in the `user_data/data/bittrex-20180101` directory. You can then use this data for backtesting as follows: @@ -223,7 +223,7 @@ You can then load the trades to perform further analysis as shown in our [data a To compare multiple strategies, a list of Strategies can be provided to backtesting. -This is limited to 1 ticker-interval per run, however, data is only loaded once from disk so if you have multiple +This is limited to 1 timeframe (ticker interval) value per run. However, data is only loaded once from disk so if you have multiple strategies you'd like to compare, this will give a nice runtime boost. All listed Strategies need to be in the same directory. diff --git a/docs/configuration.md b/docs/configuration.md index 5580b9c68..42018a499 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -47,7 +47,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `amend_last_stake_amount` | Use reduced last stake amount if necessary. [More information below](#configuring-amount-per-trade).
*Defaults to `false`.*
**Datatype:** Boolean | `last_stake_amount_min_ratio` | Defines minimum stake amount that has to be left and executed. Applies only to the last stake amount when it's amended to a reduced value (i.e. if `amend_last_stake_amount` is set to `true`). [More information below](#configuring-amount-per-trade).
*Defaults to `0.5`.*
**Datatype:** Float (as ratio) | `amount_reserve_percent` | Reserve some amount in min pair stake amount. The bot will reserve `amount_reserve_percent` + stoploss value when calculating min pair stake amount in order to avoid possible trade refusals.
*Defaults to `0.05` (5%).*
**Datatype:** Positive Float as ratio. -| `ticker_interval` | The ticker interval to use (e.g `1m`, `5m`, `15m`, `30m`, `1h` ...). [Strategy Override](#parameters-in-the-strategy).
**Datatype:** String +| `ticker_interval` | The timeframe (ticker interval) to use (e.g `1m`, `5m`, `15m`, `30m`, `1h` ...). [Strategy Override](#parameters-in-the-strategy).
**Datatype:** String | `fiat_display_currency` | Fiat currency used to show your profits. [More information below](#what-values-can-be-used-for-fiat_display_currency).
**Datatype:** String | `dry_run` | **Required.** Define if the bot must be in Dry Run or production mode.
*Defaults to `true`.*
**Datatype:** Boolean | `dry_run_wallet` | Define the starting amount in stake currency for the simulated wallet used by the bot running in the Dry Run mode.
*Defaults to `1000`.*
**Datatype:** Float @@ -113,8 +113,8 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `internals.sd_notify` | Enables use of the sd_notify protocol to tell systemd service manager about changes in the bot state and issue keep-alive pings. See [here](installation.md#7-optional-configure-freqtrade-as-a-systemd-service) for more details.
**Datatype:** Boolean | `logfile` | Specifies logfile name. Uses a rolling strategy for log file rotation for 10 files with the 1MB limit per file.
**Datatype:** String | `user_data_dir` | Directory containing user data.
*Defaults to `./user_data/`*.
**Datatype:** String -| `dataformat_ohlcv` | Data format to use to store OHLCV historic data.
*Defaults to `json`*.
**Datatype:** String -| `dataformat_trades` | Data format to use to store trades historic data.
*Defaults to `jsongz`*.
**Datatype:** String +| `dataformat_ohlcv` | Data format to use to store historical candle (OHLCV) data.
*Defaults to `json`*.
**Datatype:** String +| `dataformat_trades` | Data format to use to store historical trades data.
*Defaults to `jsongz`*.
**Datatype:** String ### Parameters in the strategy @@ -413,7 +413,7 @@ Advanced options can be configured using the `_ft_has_params` setting, which wil Available options are listed in the exchange-class as `_ft_has_default`. -For example, to test the order type `FOK` with Kraken, and modify candle_limit to 200 (so you only get 200 candles per call): +For example, to test the order type `FOK` with Kraken, and modify candle limit to 200 (so you only get 200 candles per API call): ```json "exchange": { diff --git a/docs/data-download.md b/docs/data-download.md index 76e22f4ea..903d62854 100644 --- a/docs/data-download.md +++ b/docs/data-download.md @@ -33,7 +33,7 @@ optional arguments: Specify which tickers to download. Space-separated list. Default: `1m 5m`. --erase Clean all existing data for the selected exchange/pairs/timeframes. --data-format-ohlcv {json,jsongz} - Storage format for downloaded ohlcv data. (default: `json`). + Storage format for downloaded candle (OHLCV) data. (default: `json`). --data-format-trades {json,jsongz} Storage format for downloaded trades data. (default: `jsongz`). @@ -105,7 +105,7 @@ Common arguments: ##### Example converting data -The following command will convert all ohlcv (candle) data available in `~/.freqtrade/data/binance` from json to jsongz, saving diskspace in the process. +The following command will convert all candle (OHLCV) data available in `~/.freqtrade/data/binance` from json to jsongz, saving diskspace in the process. It'll also remove original json data files (`--erase` parameter). ``` bash @@ -192,15 +192,15 @@ Then run: freqtrade download-data --exchange binance ``` -This will download ticker data for all the currency pairs you defined in `pairs.json`. +This will download historical candle (OHLCV) data for all the currency pairs you defined in `pairs.json`. ### Other Notes - To use a different directory than the exchange specific default, use `--datadir user_data/data/some_directory`. -- To change the exchange used to download the tickers, please use a different configuration file (you'll probably need to adjust ratelimits etc.) +- To change the exchange used to download the historical data from, please use a different configuration file (you'll probably need to adjust ratelimits etc.) - To use `pairs.json` from some other directory, use `--pairs-file some_other_dir/pairs.json`. -- To download ticker data for only 10 days, use `--days 10` (defaults to 30 days). -- Use `--timeframes` to specify which tickers to download. Default is `--timeframes 1m 5m` which will download 1-minute and 5-minute tickers. +- To download historical candle (OHLCV) data for only 10 days, use `--days 10` (defaults to 30 days). +- Use `--timeframes` to specify what timeframe download the historical candle (OHLCV) data for. Default is `--timeframes 1m 5m` which will download 1-minute and 5-minute data. - To use exchange, timeframe and list of pairs as defined in your configuration file, use the `-c/--config` option. With this, the script uses the whitelist defined in the config as the list of currency pairs to download data for and does not require the pairs.json file. You can combine `-c/--config` with most other options. ### Trades (tick) data diff --git a/docs/developer.md b/docs/developer.md index ef9232a59..34b2f1ba5 100644 --- a/docs/developer.md +++ b/docs/developer.md @@ -165,7 +165,7 @@ Since CCXT does not provide unification for Stoploss On Exchange yet, we'll need ### Incomplete candles -While fetching OHLCV data, we're may end up getting incomplete candles (Depending on the exchange). +While fetching candle (OHLCV) data, we may end up getting incomplete candles (depending on the exchange). To demonstrate this, we'll use daily candles (`"1d"`) to keep things simple. We query the api (`ct.fetch_ohlcv()`) for the timeframe and look at the date of the last entry. If this entry changes or shows the date of a "incomplete" candle, then we should drop this since having incomplete candles is problematic because indicators assume that only complete candles are passed to them, and will generate a lot of false buy signals. By default, we're therefore removing the last candle assuming it's incomplete. @@ -174,14 +174,14 @@ To check how the new exchange behaves, you can use the following snippet: ``` python import ccxt from datetime import datetime -from freqtrade.data.converter import parse_ticker_dataframe +from freqtrade.data.converter import ohlcv_to_dataframe ct = ccxt.binance() timeframe = "1d" pair = "XLM/BTC" # Make sure to use a pair that exists on that exchange! raw = ct.fetch_ohlcv(pair, timeframe=timeframe) # convert to dataframe -df1 = parse_ticker_dataframe(raw, timeframe, pair=pair, drop_incomplete=False) +df1 = ohlcv_to_dataframe(raw, timeframe, pair=pair, drop_incomplete=False) print(df1.tail(1)) print(datetime.utcnow()) diff --git a/docs/edge.md b/docs/edge.md index 6a301b044..721f570c7 100644 --- a/docs/edge.md +++ b/docs/edge.md @@ -156,7 +156,7 @@ Edge module has following configuration options: | `minimum_winrate` | It filters out pairs which don't have at least minimum_winrate.
This comes handy if you want to be conservative and don't comprise win rate in favour of risk reward ratio.
*Defaults to `0.60`.*
**Datatype:** Float | `minimum_expectancy` | It filters out pairs which have the expectancy lower than this number.
Having an expectancy of 0.20 means if you put 10$ on a trade you expect a 12$ return.
*Defaults to `0.20`.*
**Datatype:** Float | `min_trade_number` | When calculating *W*, *R* and *E* (expectancy) against historical data, you always want to have a minimum number of trades. The more this number is the more Edge is reliable.
Having a win rate of 100% on a single trade doesn't mean anything at all. But having a win rate of 70% over past 100 trades means clearly something.
*Defaults to `10` (it is highly recommended not to decrease this number).*
**Datatype:** Integer -| `max_trade_duration_minute` | Edge will filter out trades with long duration. If a trade is profitable after 1 month, it is hard to evaluate the strategy based on it. But if most of trades are profitable and they have maximum duration of 30 minutes, then it is clearly a good sign.
**NOTICE:** While configuring this value, you should take into consideration your ticker interval. As an example filtering out trades having duration less than one day for a strategy which has 4h interval does not make sense. Default value is set assuming your strategy interval is relatively small (1m or 5m, etc.).
*Defaults to `1440` (one day).*
**Datatype:** Integer +| `max_trade_duration_minute` | Edge will filter out trades with long duration. If a trade is profitable after 1 month, it is hard to evaluate the strategy based on it. But if most of trades are profitable and they have maximum duration of 30 minutes, then it is clearly a good sign.
**NOTICE:** While configuring this value, you should take into consideration your timeframe (ticker interval). As an example filtering out trades having duration less than one day for a strategy which has 4h interval does not make sense. Default value is set assuming your strategy interval is relatively small (1m or 5m, etc.).
*Defaults to `1440` (one day).*
**Datatype:** Integer | `remove_pumps` | Edge will remove sudden pumps in a given market while going through historical data. However, given that pumps happen very often in crypto markets, we recommend you keep this off.
*Defaults to `false`.*
**Datatype:** Boolean ## Running Edge independently diff --git a/docs/exchanges.md b/docs/exchanges.md index 70dae0aa5..66a0e96da 100644 --- a/docs/exchanges.md +++ b/docs/exchanges.md @@ -76,8 +76,8 @@ $ pip3 install web3 ### Send incomplete candles to the strategy -Most exchanges return incomplete candles via their ohlcv / klines interface. -By default, Freqtrade assumes that incomplete candles are returned and removes the last candle assuming it's an incomplete candle. +Most exchanges return current incomplete candle via their OHLCV/klines API interface. +By default, Freqtrade assumes that incomplete candle is fetched from the exchange and removes the last candle assuming it's the incomplete candle. Whether your exchange returns incomplete candles or not can be checked using [the helper script](developer.md#Incomplete-candles) from the Contributor documentation. diff --git a/docs/hyperopt.md b/docs/hyperopt.md index 9bc5888ce..abd7aa7ce 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -103,9 +103,10 @@ Place the corresponding settings into the following methods The configuration and rules are the same than for buy signals. To avoid naming collisions in the search-space, please prefix all sell-spaces with `sell-`. -#### Using ticker-interval as part of the Strategy +#### Using timeframe as a part of the Strategy -The Strategy exposes the ticker-interval as `self.ticker_interval`. The same value is available as class-attribute `HyperoptName.ticker_interval`. +The Strategy class exposes the timeframe (ticker interval) value as the `self.ticker_interval` attribute. +The same value is available as class-attribute `HyperoptName.ticker_interval`. In the case of the linked sample-value this would be `SampleHyperOpt.ticker_interval`. ## Solving a Mystery @@ -222,11 +223,11 @@ The `--spaces all` option determines that all possible parameters should be opti !!! Warning When switching parameters or changing configuration options, make sure to not use the argument `--continue` so temporary results can be removed. -### Execute Hyperopt with Different Ticker-Data Source +### Execute Hyperopt with different historical data source -If you would like to hyperopt parameters using an alternate ticker data that -you have on-disk, use the `--datadir PATH` option. Default hyperopt will -use data from directory `user_data/data`. +If you would like to hyperopt parameters using an alternate historical data set that +you have on-disk, use the `--datadir PATH` option. By default, hyperopt +uses data from directory `user_data/data`. ### Running Hyperopt with Smaller Testset @@ -380,7 +381,7 @@ As stated in the comment, you can also use it as the value of the `minimal_roi` #### Default ROI Search Space -If you are optimizing ROI, Freqtrade creates the 'roi' optimization hyperspace for you -- it's the hyperspace of components for the ROI tables. By default, each ROI table generated by the Freqtrade consists of 4 rows (steps). Hyperopt implements adaptive ranges for ROI tables with ranges for values in the ROI steps that depend on the ticker_interval used. By default the values vary in the following ranges (for some of the most used ticker intervals, values are rounded to 5 digits after the decimal point): +If you are optimizing ROI, Freqtrade creates the 'roi' optimization hyperspace for you -- it's the hyperspace of components for the ROI tables. By default, each ROI table generated by the Freqtrade consists of 4 rows (steps). Hyperopt implements adaptive ranges for ROI tables with ranges for values in the ROI steps that depend on the ticker_interval used. By default the values vary in the following ranges (for some of the most used timeframes, values are rounded to 5 digits after the decimal point): | # step | 1m | | 5m | | 1h | | 1d | | | ------ | ------ | ----------------- | -------- | ----------- | ---------- | ----------------- | ------------ | ----------------- | @@ -389,7 +390,7 @@ If you are optimizing ROI, Freqtrade creates the 'roi' optimization hyperspace f | 3 | 4...20 | 0.00387...0.01547 | 20...100 | 0.01...0.04 | 240...1200 | 0.02294...0.09177 | 5760...28800 | 0.04059...0.16237 | | 4 | 6...44 | 0.0 | 30...220 | 0.0 | 360...2640 | 0.0 | 8640...63360 | 0.0 | -These ranges should be sufficient in most cases. The minutes in the steps (ROI dict keys) are scaled linearly depending on the ticker interval used. The ROI values in the steps (ROI dict values) are scaled logarithmically depending on the ticker interval used. +These ranges should be sufficient in most cases. The minutes in the steps (ROI dict keys) are scaled linearly depending on the timeframe (ticker interval) 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. diff --git a/docs/strategy-customization.md b/docs/strategy-customization.md index 4aacd3af6..7793ea148 100644 --- a/docs/strategy-customization.md +++ b/docs/strategy-customization.md @@ -84,7 +84,7 @@ def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame Performance Note: For the best performance be frugal on the number of indicators you are using. Let uncomment only the indicator you are using in your strategies or your hyperopt configuration, otherwise you will waste your memory and CPU usage. - :param dataframe: Raw data from the exchange and parsed by parse_ticker_dataframe() + :param dataframe: Dataframe with data from the exchange :param metadata: Additional information, like the currently traded pair :return: a Dataframe with all mandatory indicators for the strategies """ @@ -284,13 +284,14 @@ If your exchange supports it, it's recommended to also set `"stoploss_on_exchang For more information on order_types please look [here](configuration.md#understand-order_types). -### Ticker interval +### Timeframe (ticker interval) This is the set of candles the bot should download and use for the analysis. Common values are `"1m"`, `"5m"`, `"15m"`, `"1h"`, however all values supported by your exchange should work. -Please note that the same buy/sell signals may work with one interval, but not the other. -This setting is accessible within the strategy by using `self.ticker_interval`. +Please note that the same buy/sell signals may work well with one timeframe, but not with the others. + +This setting is accessible within the strategy methods as the `self.ticker_interval` attribute. ### Metadata dict @@ -335,14 +336,14 @@ Please always check the mode of operation to select the correct method to get da #### Possible options for DataProvider - `available_pairs` - Property with tuples listing cached pairs with their intervals (pair, interval). -- `ohlcv(pair, timeframe)` - Currently cached ticker data for the pair, returns DataFrame or empty DataFrame. +- `ohlcv(pair, timeframe)` - Currently cached candle (OHLCV) data for the pair, returns DataFrame or empty DataFrame. - `historic_ohlcv(pair, timeframe)` - Returns historical data stored on disk. - `get_pair_dataframe(pair, timeframe)` - This is a universal method, which returns either historical data (for backtesting) or cached live data (for the Dry-Run and Live-Run modes). - `orderbook(pair, maximum)` - Returns latest orderbook data for the pair, a dict with bids/asks with a total of `maximum` entries. - `market(pair)` - Returns market data for the pair: fees, limits, precisions, activity flag, etc. See [ccxt documentation](https://github.com/ccxt/ccxt/wiki/Manual#markets) for more details on Market data structure. - `runmode` - Property containing the current runmode. -#### Example: fetch live ohlcv / historic data for the first informative pair +#### Example: fetch live / historical candle (OHLCV) data for the first informative pair ``` python if self.dp: @@ -377,8 +378,8 @@ if self.dp: ``` python if self.dp: - for pair, ticker in self.dp.available_pairs: - print(f"available {pair}, {ticker}") + for pair, timeframe in self.dp.available_pairs: + print(f"available {pair}, {timeframe}") ``` #### Get data for non-tradeable pairs diff --git a/docs/utils.md b/docs/utils.md index cdf0c31af..ec9ceeac9 100644 --- a/docs/utils.md +++ b/docs/utils.md @@ -61,8 +61,8 @@ $ freqtrade new-config --config config_binance.json ? Do you want to enable Dry-run (simulated trades)? Yes ? Please insert your stake currency: BTC ? Please insert your stake amount: 0.05 -? Please insert max_open_trades (Integer or 'unlimited'): 5 -? Please insert your ticker interval: 15m +? Please insert max_open_trades (Integer or 'unlimited'): 3 +? Please insert your timeframe (ticker interval): 5m ? Please insert your display Currency (for reporting): USD ? Select exchange binance ? Do you want to enable Telegram? No @@ -258,7 +258,7 @@ All exchanges supported by the ccxt library: _1btcxe, acx, adara, allcoin, anxpr ## List Timeframes -Use the `list-timeframes` subcommand to see the list of ticker intervals (timeframes) available for the exchange. +Use the `list-timeframes` subcommand to see the list of timeframes (ticker intervals) available for the exchange. ``` usage: freqtrade list-timeframes [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] [--userdir PATH] [--exchange EXCHANGE] [-1] diff --git a/freqtrade/commands/arguments.py b/freqtrade/commands/arguments.py index 73e77d69d..06acea69e 100644 --- a/freqtrade/commands/arguments.py +++ b/freqtrade/commands/arguments.py @@ -296,7 +296,7 @@ class Arguments: # Add convert-data subcommand convert_data_cmd = subparsers.add_parser( 'convert-data', - help='Convert OHLCV data from one format to another.', + help='Convert candle (OHLCV) data from one format to another.', parents=[_common_parser], ) convert_data_cmd.set_defaults(func=partial(start_convert_data, ohlcv=True)) @@ -305,7 +305,7 @@ class Arguments: # Add convert-trade-data subcommand convert_trade_data_cmd = subparsers.add_parser( 'convert-trade-data', - help='Convert trade-data from one format to another.', + help='Convert trade data from one format to another.', parents=[_common_parser], ) convert_trade_data_cmd.set_defaults(func=partial(start_convert_data, ohlcv=False)) diff --git a/freqtrade/commands/build_config_commands.py b/freqtrade/commands/build_config_commands.py index 1598fa2ae..58ac6ec27 100644 --- a/freqtrade/commands/build_config_commands.py +++ b/freqtrade/commands/build_config_commands.py @@ -76,7 +76,7 @@ def ask_user_config() -> Dict[str, Any]: { "type": "text", "name": "ticker_interval", - "message": "Please insert your ticker interval:", + "message": "Please insert your timeframe (ticker interval):", "default": "5m", }, { diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py index ef674c5c2..e92acef30 100644 --- a/freqtrade/commands/cli_options.py +++ b/freqtrade/commands/cli_options.py @@ -348,7 +348,7 @@ AVAILABLE_CLI_OPTIONS = { ), "dataformat_ohlcv": Arg( '--data-format-ohlcv', - help='Storage format for downloaded ohlcv data. (default: `%(default)s`).', + help='Storage format for downloaded candle (OHLCV) data. (default: `%(default)s`).', choices=constants.AVAILABLE_DATAHANDLERS, default='json' ), diff --git a/freqtrade/configuration/timerange.py b/freqtrade/configuration/timerange.py index 3db5f6217..151003999 100644 --- a/freqtrade/configuration/timerange.py +++ b/freqtrade/configuration/timerange.py @@ -45,7 +45,7 @@ class TimeRange: """ Adjust startts by candles. Applies only if no startup-candles have been available. - :param timeframe_secs: Ticker timeframe in seconds e.g. `timeframe_to_seconds('5m')` + :param timeframe_secs: Timeframe in seconds e.g. `timeframe_to_seconds('5m')` :param startup_candles: Number of candles to move start-date forward :param min_date: Minimum data date loaded. Key kriterium to decide if start-time has to be moved diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index 7972c6333..681bf6734 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -151,17 +151,17 @@ def extract_trades_of_period(dataframe: pd.DataFrame, trades: pd.DataFrame) -> p return trades -def combine_tickers_with_mean(tickers: Dict[str, pd.DataFrame], - column: str = "close") -> pd.DataFrame: +def combine_dataframes_with_mean(data: Dict[str, pd.DataFrame], + column: str = "close") -> pd.DataFrame: """ Combine multiple dataframes "column" - :param tickers: Dict of Dataframes, dict key should be pair. + :param data: Dict of Dataframes, dict key should be pair. :param column: Column in the original dataframes to use :return: DataFrame with the column renamed to the dict key, and a column named mean, containing the mean of all pairs. """ - df_comb = pd.concat([tickers[pair].set_index('date').rename( - {column: pair}, axis=1)[pair] for pair in tickers], axis=1) + df_comb = pd.concat([data[pair].set_index('date').rename( + {column: pair}, axis=1)[pair] for pair in data], axis=1) df_comb['mean'] = df_comb.mean(axis=1) diff --git a/freqtrade/data/converter.py b/freqtrade/data/converter.py index 49a2a25bc..77371bf27 100644 --- a/freqtrade/data/converter.py +++ b/freqtrade/data/converter.py @@ -13,12 +13,12 @@ from freqtrade.constants import DEFAULT_DATAFRAME_COLUMNS logger = logging.getLogger(__name__) -def parse_ticker_dataframe(ticker: list, timeframe: str, pair: str, *, - fill_missing: bool = True, - drop_incomplete: bool = True) -> DataFrame: +def ohlcv_to_dataframe(ohlcv: list, timeframe: str, pair: str, *, + fill_missing: bool = True, drop_incomplete: bool = True) -> DataFrame: """ - Converts a ticker-list (format ccxt.fetch_ohlcv) to a Dataframe - :param ticker: ticker list, as returned by exchange.async_get_candle_history + Converts a list with candle (OHLCV) data (in format returned by ccxt.fetch_ohlcv) + to a Dataframe + :param ohlcv: list with candle (OHLCV) data, as returned by exchange.async_get_candle_history :param timeframe: timeframe (e.g. 5m). Used to fill up eventual missing data :param pair: Pair this data is for (used to warn if fillup was necessary) :param fill_missing: fill up missing candles with 0 candles @@ -26,21 +26,18 @@ def parse_ticker_dataframe(ticker: list, timeframe: str, pair: str, *, :param drop_incomplete: Drop the last candle of the dataframe, assuming it's incomplete :return: DataFrame """ - logger.debug("Parsing tickerlist to dataframe") + logger.debug(f"Converting candle (OHLCV) data to dataframe for pair {pair}.") cols = DEFAULT_DATAFRAME_COLUMNS - frame = DataFrame(ticker, columns=cols) + df = DataFrame(ohlcv, columns=cols) - frame['date'] = to_datetime(frame['date'], - unit='ms', - utc=True, - infer_datetime_format=True) + df['date'] = to_datetime(df['date'], unit='ms', utc=True, infer_datetime_format=True) - # Some exchanges return int values for volume and even for ohlc. + # Some exchanges return int values for Volume and even for OHLC. # Convert them since TA-LIB indicators used in the strategy assume floats # and fail with exception... - frame = frame.astype(dtype={'open': 'float', 'high': 'float', 'low': 'float', 'close': 'float', - 'volume': 'float'}) - return clean_ohlcv_dataframe(frame, timeframe, pair, + df = df.astype(dtype={'open': 'float', 'high': 'float', 'low': 'float', 'close': 'float', + 'volume': 'float'}) + return clean_ohlcv_dataframe(df, timeframe, pair, fill_missing=fill_missing, drop_incomplete=drop_incomplete) @@ -49,11 +46,11 @@ def clean_ohlcv_dataframe(data: DataFrame, timeframe: str, pair: str, *, fill_missing: bool = True, drop_incomplete: bool = True) -> DataFrame: """ - Clense a ohlcv dataframe by + Clense a OHLCV dataframe by * Grouping it by date (removes duplicate tics) * dropping last candles if requested * Filling up missing data (if requested) - :param data: DataFrame containing ohlcv data. + :param data: DataFrame containing candle (OHLCV) data. :param timeframe: timeframe (e.g. 5m). Used to fill up eventual missing data :param pair: Pair this data is for (used to warn if fillup was necessary) :param fill_missing: fill up missing candles with 0 candles @@ -88,16 +85,16 @@ def ohlcv_fill_up_missing_data(dataframe: DataFrame, timeframe: str, pair: str) """ from freqtrade.exchange import timeframe_to_minutes - ohlc_dict = { + ohlcv_dict = { 'open': 'first', 'high': 'max', 'low': 'min', 'close': 'last', 'volume': 'sum' } - ticker_minutes = timeframe_to_minutes(timeframe) + timeframe_minutes = timeframe_to_minutes(timeframe) # Resample to create "NAN" values - df = dataframe.resample(f'{ticker_minutes}min', on='date').agg(ohlc_dict) + df = dataframe.resample(f'{timeframe_minutes}min', on='date').agg(ohlcv_dict) # Forwardfill close for missing columns df['close'] = df['close'].fillna(method='ffill') @@ -159,20 +156,20 @@ def order_book_to_dataframe(bids: list, asks: list) -> DataFrame: def trades_to_ohlcv(trades: list, timeframe: str) -> DataFrame: """ - Converts trades list to ohlcv list + Converts trades list to OHLCV list TODO: This should get a dedicated test :param trades: List of trades, as returned by ccxt.fetch_trades. - :param timeframe: Ticker timeframe to resample data to - :return: ohlcv Dataframe. + :param timeframe: Timeframe to resample data to + :return: OHLCV Dataframe. """ from freqtrade.exchange import timeframe_to_minutes - ticker_minutes = timeframe_to_minutes(timeframe) + timeframe_minutes = timeframe_to_minutes(timeframe) df = pd.DataFrame(trades) df['datetime'] = pd.to_datetime(df['datetime']) df = df.set_index('datetime') - df_new = df['price'].resample(f'{ticker_minutes}min').ohlc() - df_new['volume'] = df['amount'].resample(f'{ticker_minutes}min').sum() + df_new = df['price'].resample(f'{timeframe_minutes}min').ohlc() + df_new['volume'] = df['amount'].resample(f'{timeframe_minutes}min').sum() df_new['date'] = df_new.index # Drop 0 volume rows df_new = df_new.dropna() @@ -206,7 +203,7 @@ def convert_trades_format(config: Dict[str, Any], convert_from: str, convert_to: def convert_ohlcv_format(config: Dict[str, Any], convert_from: str, convert_to: str, erase: bool): """ - Convert ohlcv from one format to another format. + Convert OHLCV from one format to another :param config: Config dictionary :param convert_from: Source format :param convert_to: Target format @@ -216,7 +213,7 @@ def convert_ohlcv_format(config: Dict[str, Any], convert_from: str, convert_to: src = get_datahandler(config['datadir'], convert_from) trg = get_datahandler(config['datadir'], convert_to) timeframes = config.get('timeframes', [config.get('ticker_interval')]) - logger.info(f"Converting OHLCV for timeframe {timeframes}") + logger.info(f"Converting candle (OHLCV) for timeframe {timeframes}") if 'pairs' not in config: config['pairs'] = [] @@ -224,7 +221,7 @@ def convert_ohlcv_format(config: Dict[str, Any], convert_from: str, convert_to: for timeframe in timeframes: config['pairs'].extend(src.ohlcv_get_pairs(config['datadir'], timeframe)) - logger.info(f"Converting OHLCV for {config['pairs']}") + logger.info(f"Converting candle (OHLCV) data for {config['pairs']}") for timeframe in timeframes: for pair in config['pairs']: diff --git a/freqtrade/data/dataprovider.py b/freqtrade/data/dataprovider.py index 2964d1cb7..1df710152 100644 --- a/freqtrade/data/dataprovider.py +++ b/freqtrade/data/dataprovider.py @@ -1,7 +1,7 @@ """ Dataprovider Responsible to provide data to the bot -including Klines, tickers, historic data +including ticker and orderbook data, live and historical candle (OHLCV) data Common Interface for bot and strategy to access data. """ import logging @@ -43,10 +43,10 @@ class DataProvider: def ohlcv(self, pair: str, timeframe: str = None, copy: bool = True) -> DataFrame: """ - Get ohlcv data for the given pair as DataFrame + Get candle (OHLCV) data for the given pair as DataFrame Please use the `available_pairs` method to verify which pairs are currently cached. :param pair: pair to get the data for - :param timeframe: Ticker timeframe to get data for + :param timeframe: Timeframe to get data for :param copy: copy dataframe before returning if True. Use False only for read-only operations (where the dataframe is not modified) """ @@ -58,7 +58,7 @@ class DataProvider: def historic_ohlcv(self, pair: str, timeframe: str = None) -> DataFrame: """ - Get stored historic ohlcv data + Get stored historical candle (OHLCV) data :param pair: pair to get the data for :param timeframe: timeframe to get data for """ @@ -69,17 +69,17 @@ class DataProvider: def get_pair_dataframe(self, pair: str, timeframe: str = None) -> DataFrame: """ - Return pair ohlcv data, either live or cached historical -- depending + Return pair candle (OHLCV) data, either live or cached historical -- depending on the runmode. :param pair: pair to get the data for :param timeframe: timeframe to get data for :return: Dataframe for this pair """ if self.runmode in (RunMode.DRY_RUN, RunMode.LIVE): - # Get live ohlcv data. + # Get live OHLCV data. data = self.ohlcv(pair=pair, timeframe=timeframe) else: - # Get historic ohlcv data (cached on disk). + # Get historical OHLCV data (cached on disk). data = self.historic_ohlcv(pair=pair, timeframe=timeframe) if len(data) == 0: logger.warning(f"No data found for ({pair}, {timeframe}).") diff --git a/freqtrade/data/history/history_utils.py b/freqtrade/data/history/history_utils.py index 5f9a7da20..89d29d33b 100644 --- a/freqtrade/data/history/history_utils.py +++ b/freqtrade/data/history/history_utils.py @@ -9,7 +9,7 @@ from pandas import DataFrame from freqtrade.configuration import TimeRange from freqtrade.constants import DEFAULT_DATAFRAME_COLUMNS -from freqtrade.data.converter import parse_ticker_dataframe, trades_to_ohlcv +from freqtrade.data.converter import ohlcv_to_dataframe, trades_to_ohlcv from freqtrade.data.history.idatahandler import IDataHandler, get_datahandler from freqtrade.exceptions import OperationalException from freqtrade.exchange import Exchange @@ -28,10 +28,10 @@ def load_pair_history(pair: str, data_handler: IDataHandler = None, ) -> DataFrame: """ - Load cached ticker history for the given pair. + Load cached ohlcv history for the given pair. :param pair: Pair to load data for - :param timeframe: Ticker timeframe (e.g. "5m") + :param timeframe: Timeframe (e.g. "5m") :param datadir: Path to the data storage location. :param data_format: Format of the data. Ignored if data_handler is set. :param timerange: Limit data to be loaded to this timerange @@ -63,10 +63,10 @@ def load_data(datadir: Path, data_format: str = 'json', ) -> Dict[str, DataFrame]: """ - Load ticker history data for a list of pairs. + Load ohlcv history data for a list of pairs. :param datadir: Path to the data storage location. - :param timeframe: Ticker Timeframe (e.g. "5m") + :param timeframe: Timeframe (e.g. "5m") :param pairs: List of pairs to load :param timerange: Limit data to be loaded to this timerange :param fill_up_missing: Fill missing values with "No action"-candles @@ -104,10 +104,10 @@ def refresh_data(datadir: Path, timerange: Optional[TimeRange] = None, ) -> None: """ - Refresh ticker history data for a list of pairs. + Refresh ohlcv history data for a list of pairs. :param datadir: Path to the data storage location. - :param timeframe: Ticker Timeframe (e.g. "5m") + :param timeframe: Timeframe (e.g. "5m") :param pairs: List of pairs to load :param exchange: Exchange object :param timerange: Limit data to be loaded to this timerange @@ -165,7 +165,7 @@ def _download_pair_history(datadir: Path, Based on @Rybolov work: https://github.com/rybolov/freqtrade-data :param pair: pair to download - :param timeframe: Ticker Timeframe (e.g 5m) + :param timeframe: Timeframe (e.g "5m") :param timerange: range of time to download :return: bool with success state """ @@ -194,8 +194,8 @@ def _download_pair_history(datadir: Path, days=-30).float_timestamp) * 1000 ) # TODO: Maybe move parsing to exchange class (?) - new_dataframe = parse_ticker_dataframe(new_data, timeframe, pair, - fill_missing=False, drop_incomplete=True) + new_dataframe = ohlcv_to_dataframe(new_data, timeframe, pair, + fill_missing=False, drop_incomplete=True) if data.empty: data = new_dataframe else: @@ -362,7 +362,7 @@ def validate_backtest_data(data: DataFrame, pair: str, min_date: datetime, :param pair: pair used for log output. :param min_date: start-date of the data :param max_date: end-date of the data - :param timeframe_min: ticker Timeframe in minutes + :param timeframe_min: Timeframe in minutes """ # total difference in minutes / timeframe-minutes expected_frames = int((max_date - min_date).total_seconds() // 60 // timeframe_min) diff --git a/freqtrade/data/history/idatahandler.py b/freqtrade/data/history/idatahandler.py index df03e7713..b08292604 100644 --- a/freqtrade/data/history/idatahandler.py +++ b/freqtrade/data/history/idatahandler.py @@ -55,7 +55,7 @@ class IDataHandler(ABC): Implements the loading and conversion to a Pandas dataframe. Timerange trimming and dataframe validation happens outside of this method. :param pair: Pair to load data - :param timeframe: Ticker timeframe (e.g. "5m") + :param timeframe: Timeframe (e.g. "5m") :param timerange: Limit data to be loaded to this timerange. Optionally implemented by subclasses to avoid loading all data where possible. @@ -67,7 +67,7 @@ class IDataHandler(ABC): """ Remove data for this pair :param pair: Delete data for this pair. - :param timeframe: Ticker timeframe (e.g. "5m") + :param timeframe: Timeframe (e.g. "5m") :return: True when deleted, false if file did not exist. """ @@ -129,10 +129,10 @@ class IDataHandler(ABC): warn_no_data: bool = True ) -> DataFrame: """ - Load cached ticker history for the given pair. + Load cached candle (OHLCV) data for the given pair. :param pair: Pair to load data for - :param timeframe: Ticker timeframe (e.g. "5m") + :param timeframe: Timeframe (e.g. "5m") :param timerange: Limit data to be loaded to this timerange :param fill_missing: Fill missing values with "No action"-candles :param drop_incomplete: Drop last candle assuming it may be incomplete. @@ -145,28 +145,27 @@ class IDataHandler(ABC): if startup_candles > 0 and timerange_startup: timerange_startup.subtract_start(timeframe_to_seconds(timeframe) * startup_candles) - pairdf = self._ohlcv_load(pair, timeframe, - timerange=timerange_startup) - if pairdf.empty: + df = self._ohlcv_load(pair, timeframe, timerange=timerange_startup) + if df.empty: if warn_no_data: logger.warning( f'No history data for pair: "{pair}", timeframe: {timeframe}. ' 'Use `freqtrade download-data` to download the data' ) - return pairdf + return df else: - enddate = pairdf.iloc[-1]['date'] + enddate = df.iloc[-1]['date'] if timerange_startup: - self._validate_pairdata(pair, pairdf, timerange_startup) - pairdf = trim_dataframe(pairdf, timerange_startup) + self._validate_pairdata(pair, df, timerange_startup) + df = trim_dataframe(df, timerange_startup) # incomplete candles should only be dropped if we didn't trim the end beforehand. - return clean_ohlcv_dataframe(pairdf, timeframe, + return clean_ohlcv_dataframe(df, timeframe, pair=pair, fill_missing=fill_missing, drop_incomplete=(drop_incomplete and - enddate == pairdf.iloc[-1]['date'])) + enddate == df.iloc[-1]['date'])) def _validate_pairdata(self, pair, pairdata: DataFrame, timerange: TimeRange): """ diff --git a/freqtrade/data/history/jsondatahandler.py b/freqtrade/data/history/jsondatahandler.py index 2b738a94a..363b03958 100644 --- a/freqtrade/data/history/jsondatahandler.py +++ b/freqtrade/data/history/jsondatahandler.py @@ -60,7 +60,7 @@ class JsonDataHandler(IDataHandler): Implements the loading and conversion to a Pandas dataframe. Timerange trimming and dataframe validation happens outside of this method. :param pair: Pair to load data - :param timeframe: Ticker timeframe (e.g. "5m") + :param timeframe: Timeframe (e.g. "5m") :param timerange: Limit data to be loaded to this timerange. Optionally implemented by subclasses to avoid loading all data where possible. @@ -83,7 +83,7 @@ class JsonDataHandler(IDataHandler): """ Remove data for this pair :param pair: Delete data for this pair. - :param timeframe: Ticker timeframe (e.g. "5m") + :param timeframe: Timeframe (e.g. "5m") :return: True when deleted, false if file did not exist. """ filename = self._pair_data_filename(self._datadir, pair, timeframe) diff --git a/freqtrade/edge/edge_positioning.py b/freqtrade/edge/edge_positioning.py index 57a8f4a7c..a24e29efb 100644 --- a/freqtrade/edge/edge_positioning.py +++ b/freqtrade/edge/edge_positioning.py @@ -119,7 +119,7 @@ class Edge: logger.critical("No data found. Edge is stopped ...") return False - preprocessed = self.strategy.tickerdata_to_dataframe(data) + preprocessed = self.strategy.ohlcvdata_to_dataframe(data) # Print timeframe min_date, max_date = history.get_timerange(preprocessed) @@ -137,10 +137,10 @@ class Edge: pair_data = pair_data.sort_values(by=['date']) pair_data = pair_data.reset_index(drop=True) - ticker_data = self.strategy.advise_sell( + dataframe = self.strategy.advise_sell( self.strategy.advise_buy(pair_data, {'pair': pair}), {'pair': pair})[headers].copy() - trades += self._find_trades_for_stoploss_range(ticker_data, pair, self._stoploss_range) + trades += self._find_trades_for_stoploss_range(dataframe, pair, self._stoploss_range) # If no trade found then exit if len(trades) == 0: @@ -359,11 +359,11 @@ class Edge: # Returning a list of pairs in order of "expectancy" return final - def _find_trades_for_stoploss_range(self, ticker_data, pair, stoploss_range): - buy_column = ticker_data['buy'].values - sell_column = ticker_data['sell'].values - date_column = ticker_data['date'].values - ohlc_columns = ticker_data[['open', 'high', 'low', 'close']].values + def _find_trades_for_stoploss_range(self, dataframe, pair, stoploss_range): + buy_column = dataframe['buy'].values + sell_column = dataframe['sell'].values + date_column = dataframe['date'].values + ohlc_columns = dataframe[['open', 'high', 'low', 'close']].values result: list = [] for stoploss in stoploss_range: diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 522b4e40e..f4c94a1ca 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -18,7 +18,7 @@ from ccxt.base.decimal_to_precision import (ROUND_DOWN, ROUND_UP, TICK_SIZE, TRUNCATE, decimal_to_precision) from pandas import DataFrame -from freqtrade.data.converter import parse_ticker_dataframe +from freqtrade.data.converter import ohlcv_to_dataframe from freqtrade.exceptions import (DependencyException, InvalidOrderException, OperationalException, TemporaryError) from freqtrade.exchange.common import BAD_EXCHANGES, retrier, retrier_async @@ -351,7 +351,7 @@ class Exchange: def validate_timeframes(self, timeframe: Optional[str]) -> None: """ - Checks if ticker interval from config is a supported timeframe on the exchange + Check if timeframe from config is a supported timeframe on the exchange """ if not hasattr(self._api, "timeframes") or self._api.timeframes is None: # If timeframes attribute is missing (or is None), the exchange probably @@ -364,7 +364,7 @@ class Exchange: if timeframe and (timeframe not in self.timeframes): raise OperationalException( - f"Invalid ticker interval '{timeframe}'. This exchange supports: {self.timeframes}") + f"Invalid timeframe '{timeframe}'. This exchange supports: {self.timeframes}") if timeframe and timeframe_to_minutes(timeframe) < 1: raise OperationalException( @@ -599,7 +599,7 @@ class Exchange: return self._api.fetch_tickers() except ccxt.NotSupported as e: raise OperationalException( - f'Exchange {self._api.name} does not support fetching tickers in batch.' + f'Exchange {self._api.name} does not support fetching tickers in batch. ' f'Message: {e}') from e except (ccxt.NetworkError, ccxt.ExchangeError) as e: raise TemporaryError( @@ -623,13 +623,13 @@ class Exchange: def get_historic_ohlcv(self, pair: str, timeframe: str, since_ms: int) -> List: """ - Gets candle history using asyncio and returns the list of candles. - Handles all async doing. - Async over one pair, assuming we get `_ohlcv_candle_limit` candles per call. + Get candle history using asyncio and returns the list of candles. + Handles all async work for this. + Async over one pair, assuming we get `self._ohlcv_candle_limit` candles per call. :param pair: Pair to download - :param timeframe: Ticker Timeframe to get + :param timeframe: Timeframe to get data for :param since_ms: Timestamp in milliseconds to get history from - :returns List of tickers + :returns List with candle (OHLCV) data """ return asyncio.get_event_loop().run_until_complete( self._async_get_historic_ohlcv(pair=pair, timeframe=timeframe, @@ -649,26 +649,27 @@ class Exchange: pair, timeframe, since) for since in range(since_ms, arrow.utcnow().timestamp * 1000, one_call)] - tickers = await asyncio.gather(*input_coroutines, return_exceptions=True) + results = await asyncio.gather(*input_coroutines, return_exceptions=True) - # Combine tickers + # Combine gathered results data: List = [] - for p, timeframe, ticker in tickers: + for p, timeframe, res in results: if p == pair: - data.extend(ticker) + data.extend(res) # Sort data again after extending the result - above calls return in "async order" data = sorted(data, key=lambda x: x[0]) - logger.info("downloaded %s with length %s.", pair, len(data)) + logger.info("Downloaded data for %s with length %s.", pair, len(data)) return data def refresh_latest_ohlcv(self, pair_list: List[Tuple[str, str]]) -> List[Tuple[str, List]]: """ - Refresh in-memory ohlcv asynchronously and set `_klines` with the result + Refresh in-memory OHLCV asynchronously and set `_klines` with the result Loops asynchronously over pair_list and downloads all pairs async (semi-parallel). + Only used in the dataprovider.refresh() method. :param pair_list: List of 2 element tuples containing pair, interval to refresh - :return: Returns a List of ticker-dataframes. + :return: TODO: return value is only used in the tests, get rid of it """ - logger.debug("Refreshing ohlcv data for %d pairs", len(pair_list)) + logger.debug("Refreshing candle (OHLCV) data for %d pairs", len(pair_list)) input_coroutines = [] @@ -679,15 +680,15 @@ class Exchange: input_coroutines.append(self._async_get_candle_history(pair, timeframe)) else: logger.debug( - "Using cached ohlcv data for pair %s, timeframe %s ...", + "Using cached candle (OHLCV) data for pair %s, timeframe %s ...", pair, timeframe ) - tickers = asyncio.get_event_loop().run_until_complete( + results = asyncio.get_event_loop().run_until_complete( asyncio.gather(*input_coroutines, return_exceptions=True)) # handle caching - for res in tickers: + for res in results: if isinstance(res, Exception): logger.warning("Async code raised an exception: %s", res.__class__.__name__) continue @@ -698,13 +699,14 @@ class Exchange: if ticks: self._pairs_last_refresh_time[(pair, timeframe)] = ticks[-1][0] // 1000 # keeping parsed dataframe in cache - self._klines[(pair, timeframe)] = parse_ticker_dataframe( + self._klines[(pair, timeframe)] = ohlcv_to_dataframe( ticks, timeframe, pair=pair, fill_missing=True, drop_incomplete=self._ohlcv_partial_candle) - return tickers + + return results def _now_is_time_to_refresh(self, pair: str, timeframe: str) -> bool: - # Calculating ticker interval in seconds + # Timeframe in seconds interval_in_sec = timeframe_to_seconds(timeframe) return not ((self._pairs_last_refresh_time.get((pair, timeframe), 0) @@ -714,11 +716,11 @@ class Exchange: async def _async_get_candle_history(self, pair: str, timeframe: str, since_ms: Optional[int] = None) -> Tuple[str, str, List]: """ - Asynchronously gets candle histories using fetch_ohlcv + Asynchronously get candle history data using fetch_ohlcv returns tuple: (pair, timeframe, ohlcv_list) """ try: - # fetch ohlcv asynchronously + # Fetch OHLCV asynchronously s = '(' + arrow.get(since_ms // 1000).isoformat() + ') ' if since_ms is not None else '' logger.debug( "Fetching pair %s, interval %s, since %s %s...", @@ -728,9 +730,9 @@ class Exchange: data = await self._api_async.fetch_ohlcv(pair, timeframe=timeframe, since=since_ms) - # Because some exchange sort Tickers ASC and other DESC. - # Ex: Bittrex returns a list of tickers ASC (oldest first, newest last) - # when GDAX returns a list of tickers DESC (newest first, oldest last) + # Some exchanges sort OHLCV in ASC order and others in DESC. + # Ex: Bittrex returns the list of OHLCV in ASC order (oldest first, newest last) + # while GDAX returns the list of OHLCV in DESC order (newest first, oldest last) # Only sort if necessary to save computing time try: if data and data[0][0] > data[-1][0]: @@ -743,14 +745,15 @@ class Exchange: except ccxt.NotSupported as e: raise OperationalException( - f'Exchange {self._api.name} does not support fetching historical candlestick data.' - f'Message: {e}') from e + f'Exchange {self._api.name} does not support fetching historical ' + f'candle (OHLCV) data. Message: {e}') from e except (ccxt.NetworkError, ccxt.ExchangeError) as e: - raise TemporaryError(f'Could not load ticker history for pair {pair} due to ' - f'{e.__class__.__name__}. Message: {e}') from e + raise TemporaryError(f'Could not fetch historical candle (OHLCV) data ' + f'for pair {pair} due to {e.__class__.__name__}. ' + f'Message: {e}') from e except ccxt.BaseError as e: - raise OperationalException(f'Could not fetch ticker data for pair {pair}. ' - f'Msg: {e}') from e + raise OperationalException(f'Could not fetch historical candle (OHLCV) data ' + f'for pair {pair}. Message: {e}') from e @retrier_async async def _async_fetch_trades(self, pair: str, @@ -883,14 +886,14 @@ class Exchange: until: Optional[int] = None, from_id: Optional[str] = None) -> Tuple[str, List]: """ - Gets candle history using asyncio and returns the list of candles. - Handles all async doing. - Async over one pair, assuming we get `_ohlcv_candle_limit` candles per call. + Get trade history data using asyncio. + Handles all async work and returns the list of candles. + Async over one pair, assuming we get `self._ohlcv_candle_limit` candles per call. :param pair: Pair to download :param since: Timestamp in milliseconds to get history from :param until: Timestamp in milliseconds. Defaults to current timestamp if not defined. :param from_id: Download data starting with ID (if id is known) - :returns List of tickers + :returns List of trade data """ if not self.exchange_has("fetchTrades"): raise OperationalException("This exchange does not suport downloading Trades.") diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 914b8d9cd..9897b39b4 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -172,8 +172,8 @@ class FreqtradeBot: _whitelist = self.edge.adjust(_whitelist) if trades: - # Extend active-pair whitelist with pairs from open trades - # It ensures that tickers are downloaded for open trades + # Extend active-pair whitelist with pairs of open trades + # It ensures that candle (OHLCV) data are downloaded for open trades as well _whitelist.extend([trade.pair for trade in trades if trade.pair not in _whitelist]) return _whitelist @@ -628,7 +628,7 @@ class FreqtradeBot: def get_sell_rate(self, pair: str, refresh: bool) -> float: """ - Get sell rate - either using get-ticker bid or first bid based on orderbook + Get sell rate - either using ticker bid or first bid based on orderbook The orderbook portion is only used for rpc messaging, which would otherwise fail for BitMex (has no bid/ask in fetch_ticker) or remain static in any other case since it's not updating. @@ -1043,7 +1043,7 @@ class FreqtradeBot: """ profit_rate = trade.close_rate if trade.close_rate else trade.close_rate_requested profit_trade = trade.calc_profit(rate=profit_rate) - # Use cached ticker here - it was updated seconds ago. + # Use cached rates here - it was updated seconds ago. current_rate = self.get_sell_rate(trade.pair, False) profit_ratio = trade.calc_profit_ratio(profit_rate) gain = "profit" if profit_ratio > 0 else "loss" diff --git a/freqtrade/misc.py b/freqtrade/misc.py index 96bac28d8..1f52b75ec 100644 --- a/freqtrade/misc.py +++ b/freqtrade/misc.py @@ -81,13 +81,13 @@ def file_load_json(file): gzipfile = file # Try gzip file first, otherwise regular json file. if gzipfile.is_file(): - logger.debug('Loading ticker data from file %s', gzipfile) - with gzip.open(gzipfile) as tickerdata: - pairdata = json_load(tickerdata) + logger.debug(f"Loading historical data from file {gzipfile}") + with gzip.open(gzipfile) as datafile: + pairdata = json_load(datafile) elif file.is_file(): - logger.debug('Loading ticker data from file %s', file) - with open(file) as tickerdata: - pairdata = json_load(tickerdata) + logger.debug(f"Loading historical data from file {file}") + with open(file) as datafile: + pairdata = json_load(datafile) else: return None return pairdata diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 94441ce24..949c072c5 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -88,8 +88,8 @@ class Backtesting: validate_config_consistency(self.config) if "ticker_interval" not in self.config: - raise OperationalException("Ticker-interval needs to be set in either configuration " - "or as cli argument `--ticker-interval 5m`") + raise OperationalException("Timeframe (ticker interval) needs to be set in either " + "configuration or as cli argument `--ticker-interval 5m`") self.timeframe = str(self.config.get('ticker_interval')) self.timeframe_min = timeframe_to_minutes(self.timeframe) @@ -151,32 +151,33 @@ class Backtesting: logger.info(f'Dumping backtest results to {recordfilename}') file_dump_json(recordfilename, records) - def _get_ticker_list(self, processed: Dict) -> Dict[str, DataFrame]: + def _get_ohlcv_as_lists(self, processed: Dict) -> Dict[str, DataFrame]: """ - Helper function to convert a processed tickerlist into a list for performance reasons. + Helper function to convert a processed dataframes into lists for performance reasons. Used by backtest() - so keep this optimized for performance. """ headers = ['date', 'buy', 'open', 'close', 'sell', 'low', 'high'] - ticker: Dict = {} - # Create ticker dict + data: Dict = {} + # Create dict with data for pair, pair_data in processed.items(): pair_data.loc[:, 'buy'] = 0 # cleanup from previous run pair_data.loc[:, 'sell'] = 0 # cleanup from previous run - ticker_data = self.strategy.advise_sell( + dataframe = self.strategy.advise_sell( self.strategy.advise_buy(pair_data, {'pair': pair}), {'pair': pair})[headers].copy() - # to avoid using data from future, we buy/sell with signal from previous candle - ticker_data.loc[:, 'buy'] = ticker_data['buy'].shift(1) - ticker_data.loc[:, 'sell'] = ticker_data['sell'].shift(1) + # To avoid using data from future, we use buy/sell signals shifted + # from the previous candle + dataframe.loc[:, 'buy'] = dataframe['buy'].shift(1) + dataframe.loc[:, 'sell'] = dataframe['sell'].shift(1) - ticker_data.drop(ticker_data.head(1).index, inplace=True) + dataframe.drop(dataframe.head(1).index, inplace=True) # Convert from Pandas to list for performance reasons # (Looping Pandas is slow.) - ticker[pair] = [x for x in ticker_data.itertuples()] - return ticker + data[pair] = [x for x in dataframe.itertuples()] + return data def _get_close_rate(self, sell_row, trade: Trade, sell: SellCheckTuple, trade_dur: int) -> float: @@ -220,7 +221,7 @@ class Backtesting: def _get_sell_trade_entry( self, pair: str, buy_row: DataFrame, - partial_ticker: List, trade_count_lock: Dict, + partial_ohlcv: List, trade_count_lock: Dict, stake_amount: float, max_open_trades: int) -> Optional[BacktestResult]: trade = Trade( @@ -235,7 +236,7 @@ class Backtesting: ) logger.debug(f"{pair} - Backtesting emulates creation of new trade: {trade}.") # calculate win/lose forwards from buy point - for sell_row in partial_ticker: + for sell_row in partial_ohlcv: if max_open_trades > 0: # Increase trade_count_lock for every iteration trade_count_lock[sell_row.date] = trade_count_lock.get(sell_row.date, 0) + 1 @@ -259,9 +260,9 @@ class Backtesting: close_rate=closerate, sell_reason=sell.sell_type ) - if partial_ticker: + if partial_ohlcv: # no sell condition found - trade stil open at end of backtest period - sell_row = partial_ticker[-1] + sell_row = partial_ohlcv[-1] bt_res = BacktestResult(pair=pair, profit_percent=trade.calc_profit_ratio(rate=sell_row.open), profit_abs=trade.calc_profit(rate=sell_row.open), @@ -308,8 +309,9 @@ class Backtesting: trades = [] trade_count_lock: Dict = {} - # Dict of ticker-lists for performance (looping lists is a lot faster than dataframes) - ticker: Dict = self._get_ticker_list(processed) + # Use dict of lists with data for performance + # (looping lists is a lot faster than pandas DataFrames) + data: Dict = self._get_ohlcv_as_lists(processed) lock_pair_until: Dict = {} # Indexes per pair, so some pairs are allowed to have a missing start. @@ -319,12 +321,12 @@ class Backtesting: # Loop timerange and get candle for each pair at that point in time while tmp < end_date: - for i, pair in enumerate(ticker): + for i, pair in enumerate(data): if pair not in indexes: indexes[pair] = 0 try: - row = ticker[pair][indexes[pair]] + row = data[pair][indexes[pair]] except IndexError: # missing Data for one pair at the end. # Warnings for this are shown during data loading @@ -352,7 +354,7 @@ class Backtesting: # since indexes has been incremented before, we need to go one step back to # also check the buying candle for sell conditions. - trade_entry = self._get_sell_trade_entry(pair, row, ticker[pair][indexes[pair]-1:], + trade_entry = self._get_sell_trade_entry(pair, row, data[pair][indexes[pair]-1:], trade_count_lock, stake_amount, max_open_trades) @@ -395,7 +397,7 @@ class Backtesting: self._set_strategy(strat) # need to reprocess data every time to populate signals - preprocessed = self.strategy.tickerdata_to_dataframe(data) + preprocessed = self.strategy.ohlcvdata_to_dataframe(data) # Trim startup period from analyzed dataframe for pair, df in preprocessed.items(): diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index e9ab469f4..ed58db977 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -75,8 +75,8 @@ class Hyperopt: self.trials_file = (self.config['user_data_dir'] / 'hyperopt_results' / 'hyperopt_results.pickle') - self.tickerdata_pickle = (self.config['user_data_dir'] / - 'hyperopt_results' / 'hyperopt_tickerdata.pkl') + self.data_pickle_file = (self.config['user_data_dir'] / + 'hyperopt_results' / 'hyperopt_data.pkl') self.total_epochs = config.get('epochs', 0) self.current_best_loss = 100 @@ -130,7 +130,7 @@ class Hyperopt: """ Remove hyperopt pickle files to restart hyperopt. """ - for f in [self.tickerdata_pickle, self.trials_file]: + for f in [self.data_pickle_file, self.trials_file]: p = Path(f) if p.is_file(): logger.info(f"Removing `{p}`.") @@ -454,7 +454,7 @@ class Hyperopt: self.backtesting.strategy.trailing_only_offset_is_reached = \ d['trailing_only_offset_is_reached'] - processed = load(self.tickerdata_pickle) + processed = load(self.data_pickle_file) min_date, max_date = get_timerange(processed) @@ -570,7 +570,7 @@ class Hyperopt: self.hyperopt_table_header = -1 data, timerange = self.backtesting.load_bt_data() - preprocessed = self.backtesting.strategy.tickerdata_to_dataframe(data) + preprocessed = self.backtesting.strategy.ohlcvdata_to_dataframe(data) # Trim startup period from analyzed dataframe for pair, df in preprocessed.items(): @@ -581,7 +581,7 @@ class Hyperopt: 'Hyperopting with data from %s up to %s (%s days)..', min_date.isoformat(), max_date.isoformat(), (max_date - min_date).days ) - dump(preprocessed, self.tickerdata_pickle) + dump(preprocessed, self.data_pickle_file) # We don't need exchange instance anymore while running hyperopt self.backtesting.exchange = None # type: ignore diff --git a/freqtrade/plot/plotting.py b/freqtrade/plot/plotting.py index d979a40e0..cfbda6714 100644 --- a/freqtrade/plot/plotting.py +++ b/freqtrade/plot/plotting.py @@ -6,7 +6,7 @@ import pandas as pd from freqtrade.configuration import TimeRange from freqtrade.data.btanalysis import (calculate_max_drawdown, - combine_tickers_with_mean, + combine_dataframes_with_mean, create_cum_profit, extract_trades_of_period, load_trades) from freqtrade.data.converter import trim_dataframe @@ -29,7 +29,7 @@ except ImportError: def init_plotscript(config): """ Initialize objects needed for plotting - :return: Dict with tickers, trades and pairs + :return: Dict with candle (OHLCV) data, trades and pairs """ if "pairs" in config: @@ -40,7 +40,7 @@ def init_plotscript(config): # Set timerange to use timerange = TimeRange.parse_timerange(config.get("timerange")) - tickers = load_data( + data = load_data( datadir=config.get("datadir"), pairs=pairs, timeframe=config.get('ticker_interval', '5m'), @@ -53,7 +53,7 @@ def init_plotscript(config): exportfilename=config.get('exportfilename'), ) trades = trim_dataframe(trades, timerange, 'open_time') - return {"tickers": tickers, + return {"ohlcv": data, "trades": trades, "pairs": pairs, } @@ -368,10 +368,10 @@ def generate_candlestick_graph(pair: str, data: pd.DataFrame, trades: pd.DataFra return fig -def generate_profit_graph(pairs: str, tickers: Dict[str, pd.DataFrame], +def generate_profit_graph(pairs: str, data: Dict[str, pd.DataFrame], trades: pd.DataFrame, timeframe: str) -> go.Figure: # Combine close-values for all pairs, rename columns to "pair" - df_comb = combine_tickers_with_mean(tickers, "close") + df_comb = combine_dataframes_with_mean(data, "close") # Add combined cumulative profit df_comb = create_cum_profit(df_comb, trades, 'cum_profit', timeframe) @@ -439,7 +439,7 @@ def load_and_plot_trades(config: Dict[str, Any]): """ From configuration provided - Initializes plot-script - - Get tickers data + - Get candle (OHLCV) data - Generate Dafaframes populated with indicators and signals based on configured strategy - Load trades excecuted during the selected period - Generate Plotly plot objects @@ -451,13 +451,13 @@ def load_and_plot_trades(config: Dict[str, Any]): plot_elements = init_plotscript(config) trades = plot_elements['trades'] pair_counter = 0 - for pair, data in plot_elements["tickers"].items(): + for pair, data in plot_elements["ohlcv"].items(): pair_counter += 1 logger.info("analyse pair %s", pair) - tickers = {} - tickers[pair] = data + ohlcv = {} + ohlcv[pair] = data - dataframe = strategy.analyze_ticker(tickers[pair], {'pair': pair}) + dataframe = strategy.analyze_ticker(ohlcv[pair], {'pair': pair}) trades_pair = trades.loc[trades['pair'] == pair] trades_pair = extract_trades_of_period(dataframe, trades_pair) @@ -494,7 +494,7 @@ def plot_profit(config: Dict[str, Any]) -> None: # Create an average close price of all the pairs that were involved. # this could be useful to gauge the overall market trend - fig = generate_profit_graph(plot_elements["pairs"], plot_elements["tickers"], + fig = generate_profit_graph(plot_elements["pairs"], plot_elements["ohlcv"], trades, config.get('ticker_interval', '5m')) store_plot_file(fig, filename='freqtrade-profit-plot.html', directory=config['user_data_dir'] / "plot", auto_open=True) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index d23af3f6e..696d2b2d2 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -59,7 +59,7 @@ class IStrategy(ABC): Attributes you can use: minimal_roi -> Dict: Minimal ROI designed for the strategy stoploss -> float: optimal stoploss designed for the strategy - ticker_interval -> str: value of the ticker interval to use for the strategy + ticker_interval -> str: value of the timeframe (ticker interval) to use with the strategy """ # Strategy interface version # Default to version 2 @@ -125,7 +125,7 @@ class IStrategy(ABC): def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ Populate indicators that will be used in the Buy and Sell strategy - :param dataframe: Raw data from the exchange and parsed by parse_ticker_dataframe() + :param dataframe: DataFrame with data from the exchange :param metadata: Additional information, like the currently traded pair :return: a Dataframe with all mandatory indicators for the strategies """ @@ -200,11 +200,11 @@ class IStrategy(ABC): def analyze_ticker(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ - Parses the given ticker history and returns a populated DataFrame + Parses the given candle (OHLCV) data and returns a populated DataFrame add several TA indicators and buy signal to it - :param dataframe: Dataframe containing ticker data + :param dataframe: Dataframe containing data from exchange :param metadata: Metadata dictionary with additional data (e.g. 'pair') - :return: DataFrame with ticker data and indicator data + :return: DataFrame of candle (OHLCV) data with indicator data and signals added """ logger.debug("TA Analysis Launched") dataframe = self.advise_indicators(dataframe, metadata) @@ -214,12 +214,12 @@ class IStrategy(ABC): def _analyze_ticker_internal(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ - Parses the given ticker history and returns a populated DataFrame + Parses the given candle (OHLCV) data and returns a populated DataFrame add several TA indicators and buy signal to it WARNING: Used internally only, may skip analysis if `process_only_new_candles` is set. - :param dataframe: Dataframe containing ticker data + :param dataframe: Dataframe containing data from exchange :param metadata: Metadata dictionary with additional data (e.g. 'pair') - :return: DataFrame with ticker data and indicator data + :return: DataFrame of candle (OHLCV) data with indicator data and signals added """ pair = str(metadata.get('pair')) @@ -251,21 +251,21 @@ class IStrategy(ABC): :return: (Buy, Sell) A bool-tuple indicating buy/sell signal """ if not isinstance(dataframe, DataFrame) or dataframe.empty: - logger.warning('Empty ticker history for pair %s', pair) + logger.warning('Empty candle (OHLCV) data for pair %s', pair) return False, False try: dataframe = self._analyze_ticker_internal(dataframe, {'pair': pair}) except ValueError as error: logger.warning( - 'Unable to analyze ticker for pair %s: %s', + 'Unable to analyze candle (OHLCV) data for pair %s: %s', pair, str(error) ) return False, False except Exception as error: logger.exception( - 'Unexpected error when analyzing ticker for pair %s: %s', + 'Unexpected error when analyzing candle (OHLCV) data for pair %s: %s', pair, str(error) ) @@ -440,19 +440,19 @@ class IStrategy(ABC): else: return current_profit > roi - def tickerdata_to_dataframe(self, tickerdata: Dict[str, DataFrame]) -> Dict[str, DataFrame]: + def ohlcvdata_to_dataframe(self, data: Dict[str, DataFrame]) -> Dict[str, DataFrame]: """ - Creates a dataframe and populates indicators for given ticker data + Creates a dataframe and populates indicators for given candle (OHLCV) data Used by optimize operations only, not during dry / live runs. """ return {pair: self.advise_indicators(pair_data, {'pair': pair}) - for pair, pair_data in tickerdata.items()} + for pair, pair_data in data.items()} def advise_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ Populate indicators that will be used in the Buy and Sell strategy This method should not be overridden. - :param dataframe: Raw data from the exchange and parsed by parse_ticker_dataframe() + :param dataframe: Dataframe with data from the exchange :param metadata: Additional information, like the currently traded pair :return: a Dataframe with all mandatory indicators for the strategies """ diff --git a/freqtrade/templates/base_strategy.py.j2 b/freqtrade/templates/base_strategy.py.j2 index fbf083387..97a189ff4 100644 --- a/freqtrade/templates/base_strategy.py.j2 +++ b/freqtrade/templates/base_strategy.py.j2 @@ -99,7 +99,7 @@ class {{ strategy }}(IStrategy): Performance Note: For the best performance be frugal on the number of indicators you are using. Let uncomment only the indicator you are using in your strategies or your hyperopt configuration, otherwise you will waste your memory and CPU usage. - :param dataframe: Raw data from the exchange and parsed by parse_ticker_dataframe() + :param dataframe: Dataframe with data from the exchange :param metadata: Additional information, like the currently traded pair :return: a Dataframe with all mandatory indicators for the strategies """ diff --git a/freqtrade/templates/sample_strategy.py b/freqtrade/templates/sample_strategy.py index 17372e1e0..f78489173 100644 --- a/freqtrade/templates/sample_strategy.py +++ b/freqtrade/templates/sample_strategy.py @@ -116,7 +116,7 @@ class SampleStrategy(IStrategy): Performance Note: For the best performance be frugal on the number of indicators you are using. Let uncomment only the indicator you are using in your strategies or your hyperopt configuration, otherwise you will waste your memory and CPU usage. - :param dataframe: Raw data from the exchange and parsed by parse_ticker_dataframe() + :param dataframe: Dataframe with data from the exchange :param metadata: Additional information, like the currently traded pair :return: a Dataframe with all mandatory indicators for the strategies """ diff --git a/tests/conftest.py b/tests/conftest.py index e8e3fe9e3..64d0cd5ee 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -15,7 +15,7 @@ from telegram import Chat, Message, Update from freqtrade import constants, persistence from freqtrade.commands import Arguments -from freqtrade.data.converter import parse_ticker_dataframe +from freqtrade.data.converter import ohlcv_to_dataframe from freqtrade.edge import Edge, PairInfo from freqtrade.exchange import Exchange from freqtrade.freqtradebot import FreqtradeBot @@ -849,15 +849,15 @@ def order_book_l2(): @pytest.fixture -def ticker_history_list(): +def ohlcv_history_list(): return [ [ 1511686200000, # unix timestamp ms - 8.794e-05, # open - 8.948e-05, # high - 8.794e-05, # low - 8.88e-05, # close - 0.0877869, # volume (in quote currency) + 8.794e-05, # open + 8.948e-05, # high + 8.794e-05, # low + 8.88e-05, # close + 0.0877869, # volume (in quote currency) ], [ 1511686500000, @@ -879,8 +879,9 @@ def ticker_history_list(): @pytest.fixture -def ticker_history(ticker_history_list): - return parse_ticker_dataframe(ticker_history_list, "5m", pair="UNITTEST/BTC", fill_missing=True) +def ohlcv_history(ohlcv_history_list): + return ohlcv_to_dataframe(ohlcv_history_list, "5m", pair="UNITTEST/BTC", + fill_missing=True) @pytest.fixture @@ -1195,8 +1196,8 @@ def tickers(): @pytest.fixture def result(testdatadir): with (testdatadir / 'UNITTEST_BTC-1m.json').open('r') as data_file: - return parse_ticker_dataframe(json.load(data_file), '1m', pair="UNITTEST/BTC", - fill_missing=True) + return ohlcv_to_dataframe(json.load(data_file), '1m', pair="UNITTEST/BTC", + fill_missing=True) @pytest.fixture(scope="function") diff --git a/tests/data/test_btanalysis.py b/tests/data/test_btanalysis.py index 7e3c1f077..da5d225b9 100644 --- a/tests/data/test_btanalysis.py +++ b/tests/data/test_btanalysis.py @@ -8,7 +8,7 @@ from freqtrade.configuration import TimeRange from freqtrade.data.btanalysis import (BT_DATA_COLUMNS, analyze_trade_parallelism, calculate_max_drawdown, - combine_tickers_with_mean, + combine_dataframes_with_mean, create_cum_profit, extract_trades_of_period, load_backtest_data, load_trades, @@ -120,13 +120,10 @@ def test_load_trades(default_conf, mocker): assert bt_mock.call_count == 1 -def test_combine_tickers_with_mean(testdatadir): +def test_combine_dataframes_with_mean(testdatadir): pairs = ["ETH/BTC", "ADA/BTC"] - tickers = load_data(datadir=testdatadir, - pairs=pairs, - timeframe='5m' - ) - df = combine_tickers_with_mean(tickers) + data = load_data(datadir=testdatadir, pairs=pairs, timeframe='5m') + df = combine_dataframes_with_mean(data) assert isinstance(df, DataFrame) assert "ETH/BTC" in df.columns assert "ADA/BTC" in df.columns diff --git a/tests/data/test_converter.py b/tests/data/test_converter.py index a0ec2f46f..7dff520e0 100644 --- a/tests/data/test_converter.py +++ b/tests/data/test_converter.py @@ -5,9 +5,12 @@ from freqtrade.configuration.timerange import TimeRange from freqtrade.data.converter import (convert_ohlcv_format, convert_trades_format, ohlcv_fill_up_missing_data, - parse_ticker_dataframe, trim_dataframe) -from freqtrade.data.history import (get_timerange, load_data, - load_pair_history, validate_backtest_data) + ohlcv_to_dataframe, + trim_dataframe) +from freqtrade.data.history import (get_timerange, + load_data, + load_pair_history, + validate_backtest_data) from tests.conftest import log_has from tests.data.test_history import _backup_file, _clean_test_file @@ -16,15 +19,15 @@ def test_dataframe_correct_columns(result): assert result.columns.tolist() == ['date', 'open', 'high', 'low', 'close', 'volume'] -def test_parse_ticker_dataframe(ticker_history_list, caplog): +def test_ohlcv_to_dataframe(ohlcv_history_list, caplog): columns = ['date', 'open', 'high', 'low', 'close', 'volume'] caplog.set_level(logging.DEBUG) # Test file with BV data - dataframe = parse_ticker_dataframe(ticker_history_list, '5m', - pair="UNITTEST/BTC", fill_missing=True) + dataframe = ohlcv_to_dataframe(ohlcv_history_list, '5m', pair="UNITTEST/BTC", + fill_missing=True) assert dataframe.columns.tolist() == columns - assert log_has('Parsing tickerlist to dataframe', caplog) + assert log_has('Converting candle (OHLCV) data to dataframe for pair UNITTEST/BTC.', caplog) def test_ohlcv_fill_up_missing_data(testdatadir, caplog): @@ -84,7 +87,8 @@ def test_ohlcv_fill_up_missing_data2(caplog): ] # Generate test-data without filling missing - data = parse_ticker_dataframe(ticks, timeframe, pair="UNITTEST/BTC", fill_missing=False) + data = ohlcv_to_dataframe(ticks, timeframe, pair="UNITTEST/BTC", + fill_missing=False) assert len(data) == 3 caplog.set_level(logging.DEBUG) data2 = ohlcv_fill_up_missing_data(data, timeframe, "UNITTEST/BTC") @@ -140,14 +144,14 @@ def test_ohlcv_drop_incomplete(caplog): ] ] caplog.set_level(logging.DEBUG) - data = parse_ticker_dataframe(ticks, timeframe, pair="UNITTEST/BTC", - fill_missing=False, drop_incomplete=False) + data = ohlcv_to_dataframe(ticks, timeframe, pair="UNITTEST/BTC", + fill_missing=False, drop_incomplete=False) assert len(data) == 4 assert not log_has("Dropping last candle", caplog) # Drop last candle - data = parse_ticker_dataframe(ticks, timeframe, pair="UNITTEST/BTC", - fill_missing=False, drop_incomplete=True) + data = ohlcv_to_dataframe(ticks, timeframe, pair="UNITTEST/BTC", + fill_missing=False, drop_incomplete=True) assert len(data) == 3 assert log_has("Dropping last candle", caplog) diff --git a/tests/data/test_dataprovider.py b/tests/data/test_dataprovider.py index 1dbe20936..2b3dda188 100644 --- a/tests/data/test_dataprovider.py +++ b/tests/data/test_dataprovider.py @@ -7,19 +7,19 @@ from freqtrade.state import RunMode from tests.conftest import get_patched_exchange -def test_ohlcv(mocker, default_conf, ticker_history): +def test_ohlcv(mocker, default_conf, ohlcv_history): default_conf["runmode"] = RunMode.DRY_RUN timeframe = default_conf["ticker_interval"] exchange = get_patched_exchange(mocker, default_conf) - exchange._klines[("XRP/BTC", timeframe)] = ticker_history - exchange._klines[("UNITTEST/BTC", timeframe)] = ticker_history + exchange._klines[("XRP/BTC", timeframe)] = ohlcv_history + exchange._klines[("UNITTEST/BTC", timeframe)] = ohlcv_history dp = DataProvider(default_conf, exchange) assert dp.runmode == RunMode.DRY_RUN - assert ticker_history.equals(dp.ohlcv("UNITTEST/BTC", timeframe)) + assert ohlcv_history.equals(dp.ohlcv("UNITTEST/BTC", timeframe)) assert isinstance(dp.ohlcv("UNITTEST/BTC", timeframe), DataFrame) - assert dp.ohlcv("UNITTEST/BTC", timeframe) is not ticker_history - assert dp.ohlcv("UNITTEST/BTC", timeframe, copy=False) is ticker_history + assert dp.ohlcv("UNITTEST/BTC", timeframe) is not ohlcv_history + assert dp.ohlcv("UNITTEST/BTC", timeframe, copy=False) is ohlcv_history assert not dp.ohlcv("UNITTEST/BTC", timeframe).empty assert dp.ohlcv("NONESENSE/AAA", timeframe).empty @@ -37,8 +37,8 @@ def test_ohlcv(mocker, default_conf, ticker_history): assert dp.ohlcv("UNITTEST/BTC", timeframe).empty -def test_historic_ohlcv(mocker, default_conf, ticker_history): - historymock = MagicMock(return_value=ticker_history) +def test_historic_ohlcv(mocker, default_conf, ohlcv_history): + historymock = MagicMock(return_value=ohlcv_history) mocker.patch("freqtrade.data.dataprovider.load_pair_history", historymock) dp = DataProvider(default_conf, None) @@ -48,18 +48,18 @@ def test_historic_ohlcv(mocker, default_conf, ticker_history): assert historymock.call_args_list[0][1]["timeframe"] == "5m" -def test_get_pair_dataframe(mocker, default_conf, ticker_history): +def test_get_pair_dataframe(mocker, default_conf, ohlcv_history): default_conf["runmode"] = RunMode.DRY_RUN ticker_interval = default_conf["ticker_interval"] exchange = get_patched_exchange(mocker, default_conf) - exchange._klines[("XRP/BTC", ticker_interval)] = ticker_history - exchange._klines[("UNITTEST/BTC", ticker_interval)] = ticker_history + exchange._klines[("XRP/BTC", ticker_interval)] = ohlcv_history + exchange._klines[("UNITTEST/BTC", ticker_interval)] = ohlcv_history dp = DataProvider(default_conf, exchange) assert dp.runmode == RunMode.DRY_RUN - assert ticker_history.equals(dp.get_pair_dataframe("UNITTEST/BTC", ticker_interval)) + assert ohlcv_history.equals(dp.get_pair_dataframe("UNITTEST/BTC", ticker_interval)) assert isinstance(dp.get_pair_dataframe("UNITTEST/BTC", ticker_interval), DataFrame) - assert dp.get_pair_dataframe("UNITTEST/BTC", ticker_interval) is not ticker_history + assert dp.get_pair_dataframe("UNITTEST/BTC", ticker_interval) is not ohlcv_history assert not dp.get_pair_dataframe("UNITTEST/BTC", ticker_interval).empty assert dp.get_pair_dataframe("NONESENSE/AAA", ticker_interval).empty @@ -73,7 +73,7 @@ def test_get_pair_dataframe(mocker, default_conf, ticker_history): assert isinstance(dp.get_pair_dataframe("UNITTEST/BTC", ticker_interval), DataFrame) assert dp.get_pair_dataframe("NONESENSE/AAA", ticker_interval).empty - historymock = MagicMock(return_value=ticker_history) + historymock = MagicMock(return_value=ohlcv_history) mocker.patch("freqtrade.data.dataprovider.load_pair_history", historymock) default_conf["runmode"] = RunMode.BACKTEST dp = DataProvider(default_conf, exchange) @@ -82,11 +82,11 @@ def test_get_pair_dataframe(mocker, default_conf, ticker_history): # assert dp.get_pair_dataframe("NONESENSE/AAA", ticker_interval).empty -def test_available_pairs(mocker, default_conf, ticker_history): +def test_available_pairs(mocker, default_conf, ohlcv_history): exchange = get_patched_exchange(mocker, default_conf) ticker_interval = default_conf["ticker_interval"] - exchange._klines[("XRP/BTC", ticker_interval)] = ticker_history - exchange._klines[("UNITTEST/BTC", ticker_interval)] = ticker_history + exchange._klines[("XRP/BTC", ticker_interval)] = ohlcv_history + exchange._klines[("UNITTEST/BTC", ticker_interval)] = ohlcv_history dp = DataProvider(default_conf, exchange) assert len(dp.available_pairs) == 2 @@ -96,7 +96,7 @@ def test_available_pairs(mocker, default_conf, ticker_history): ] -def test_refresh(mocker, default_conf, ticker_history): +def test_refresh(mocker, default_conf, ohlcv_history): refresh_mock = MagicMock() mocker.patch("freqtrade.exchange.Exchange.refresh_latest_ohlcv", refresh_mock) diff --git a/tests/data/test_history.py b/tests/data/test_history.py index 9c9af9acd..12390538a 100644 --- a/tests/data/test_history.py +++ b/tests/data/test_history.py @@ -12,7 +12,7 @@ from pandas import DataFrame from pandas.testing import assert_frame_equal from freqtrade.configuration import TimeRange -from freqtrade.data.converter import parse_ticker_dataframe +from freqtrade.data.converter import ohlcv_to_dataframe from freqtrade.data.history.history_utils import ( _download_pair_history, _download_trades_history, _load_cached_data_for_updating, convert_trades_to_ohlcv, get_timerange, @@ -63,7 +63,7 @@ def _clean_test_file(file: Path) -> None: file_swp.rename(file) -def test_load_data_30min_ticker(mocker, caplog, default_conf, testdatadir) -> None: +def test_load_data_30min_timeframe(mocker, caplog, default_conf, testdatadir) -> None: ld = load_pair_history(pair='UNITTEST/BTC', timeframe='30m', datadir=testdatadir) assert isinstance(ld, DataFrame) assert not log_has( @@ -72,7 +72,7 @@ def test_load_data_30min_ticker(mocker, caplog, default_conf, testdatadir) -> No ) -def test_load_data_7min_ticker(mocker, caplog, default_conf, testdatadir) -> None: +def test_load_data_7min_timeframe(mocker, caplog, default_conf, testdatadir) -> None: ld = load_pair_history(pair='UNITTEST/BTC', timeframe='7m', datadir=testdatadir) assert isinstance(ld, DataFrame) assert ld.empty @@ -82,8 +82,8 @@ def test_load_data_7min_ticker(mocker, caplog, default_conf, testdatadir) -> Non ) -def test_load_data_1min_ticker(ticker_history, mocker, caplog, testdatadir) -> None: - mocker.patch('freqtrade.exchange.Exchange.get_historic_ohlcv', return_value=ticker_history) +def test_load_data_1min_timeframe(ohlcv_history, mocker, caplog, testdatadir) -> None: + mocker.patch('freqtrade.exchange.Exchange.get_historic_ohlcv', return_value=ohlcv_history) file = testdatadir / 'UNITTEST_BTC-1m.json' _backup_file(file, copy_file=True) load_data(datadir=testdatadir, timeframe='1m', pairs=['UNITTEST/BTC']) @@ -110,12 +110,12 @@ def test_load_data_startup_candles(mocker, caplog, default_conf, testdatadir) -> assert ltfmock.call_args_list[0][1]['timerange'].startts == timerange.startts - 20 * 60 -def test_load_data_with_new_pair_1min(ticker_history_list, mocker, caplog, +def test_load_data_with_new_pair_1min(ohlcv_history_list, mocker, caplog, default_conf, testdatadir) -> None: """ - Test load_pair_history() with 1 min ticker + Test load_pair_history() with 1 min timeframe """ - mocker.patch('freqtrade.exchange.Exchange.get_historic_ohlcv', return_value=ticker_history_list) + mocker.patch('freqtrade.exchange.Exchange.get_historic_ohlcv', return_value=ohlcv_history_list) exchange = get_patched_exchange(mocker, default_conf) file = testdatadir / 'MEME_BTC-1m.json' @@ -188,8 +188,8 @@ def test_load_cached_data_for_updating(mocker, testdatadir) -> None: with open(test_filename, "rt") as file: test_data = json.load(file) - test_data_df = parse_ticker_dataframe(test_data, '1m', 'UNITTEST/BTC', - fill_missing=False, drop_incomplete=False) + test_data_df = ohlcv_to_dataframe(test_data, '1m', 'UNITTEST/BTC', + fill_missing=False, drop_incomplete=False) # now = last cached item + 1 hour now_ts = test_data[-1][0] / 1000 + 60 * 60 mocker.patch('arrow.utcnow', return_value=arrow.get(now_ts)) @@ -230,8 +230,8 @@ def test_load_cached_data_for_updating(mocker, testdatadir) -> None: assert start_ts is None -def test_download_pair_history(ticker_history_list, mocker, default_conf, testdatadir) -> None: - mocker.patch('freqtrade.exchange.Exchange.get_historic_ohlcv', return_value=ticker_history_list) +def test_download_pair_history(ohlcv_history_list, mocker, default_conf, testdatadir) -> None: + mocker.patch('freqtrade.exchange.Exchange.get_historic_ohlcv', return_value=ohlcv_history_list) exchange = get_patched_exchange(mocker, default_conf) file1_1 = testdatadir / 'MEME_BTC-1m.json' file1_5 = testdatadir / 'MEME_BTC-5m.json' @@ -293,7 +293,7 @@ def test_download_pair_history2(mocker, default_conf, testdatadir) -> None: assert json_dump_mock.call_count == 2 -def test_download_backtesting_data_exception(ticker_history, mocker, caplog, +def test_download_backtesting_data_exception(ohlcv_history, mocker, caplog, default_conf, testdatadir) -> None: mocker.patch('freqtrade.exchange.Exchange.get_historic_ohlcv', side_effect=Exception('File Error')) @@ -321,15 +321,15 @@ def test_load_partial_missing(testdatadir, caplog) -> None: # Make sure we start fresh - test missing data at start start = arrow.get('2018-01-01T00:00:00') end = arrow.get('2018-01-11T00:00:00') - tickerdata = load_data(testdatadir, '5m', ['UNITTEST/BTC'], startup_candles=20, - timerange=TimeRange('date', 'date', start.timestamp, end.timestamp)) + data = load_data(testdatadir, '5m', ['UNITTEST/BTC'], startup_candles=20, + timerange=TimeRange('date', 'date', start.timestamp, end.timestamp)) assert log_has( 'Using indicator startup period: 20 ...', caplog ) # timedifference in 5 minutes td = ((end - start).total_seconds() // 60 // 5) + 1 - assert td != len(tickerdata['UNITTEST/BTC']) - start_real = tickerdata['UNITTEST/BTC'].iloc[0, 0] + assert td != len(data['UNITTEST/BTC']) + start_real = data['UNITTEST/BTC'].iloc[0, 0] assert log_has(f'Missing data at start for pair ' f'UNITTEST/BTC, data starts at {start_real.strftime("%Y-%m-%d %H:%M:%S")}', caplog) @@ -337,14 +337,14 @@ def test_load_partial_missing(testdatadir, caplog) -> None: caplog.clear() start = arrow.get('2018-01-10T00:00:00') end = arrow.get('2018-02-20T00:00:00') - tickerdata = load_data(datadir=testdatadir, timeframe='5m', pairs=['UNITTEST/BTC'], - timerange=TimeRange('date', 'date', start.timestamp, end.timestamp)) + data = load_data(datadir=testdatadir, timeframe='5m', pairs=['UNITTEST/BTC'], + timerange=TimeRange('date', 'date', start.timestamp, end.timestamp)) # timedifference in 5 minutes td = ((end - start).total_seconds() // 60 // 5) + 1 - assert td != len(tickerdata['UNITTEST/BTC']) + assert td != len(data['UNITTEST/BTC']) # Shift endtime with +5 - as last candle is dropped (partial candle) - end_real = arrow.get(tickerdata['UNITTEST/BTC'].iloc[-1, 0]).shift(minutes=5) + end_real = arrow.get(data['UNITTEST/BTC'].iloc[-1, 0]).shift(minutes=5) assert log_has(f'Missing data at end for pair ' f'UNITTEST/BTC, data ends at {end_real.strftime("%Y-%m-%d %H:%M:%S")}', caplog) @@ -403,7 +403,7 @@ def test_get_timerange(default_conf, mocker, testdatadir) -> None: default_conf.update({'strategy': 'DefaultStrategy'}) strategy = StrategyResolver.load_strategy(default_conf) - data = strategy.tickerdata_to_dataframe( + data = strategy.ohlcvdata_to_dataframe( load_data( datadir=testdatadir, timeframe='1m', @@ -421,7 +421,7 @@ def test_validate_backtest_data_warn(default_conf, mocker, caplog, testdatadir) default_conf.update({'strategy': 'DefaultStrategy'}) strategy = StrategyResolver.load_strategy(default_conf) - data = strategy.tickerdata_to_dataframe( + data = strategy.ohlcvdata_to_dataframe( load_data( datadir=testdatadir, timeframe='1m', @@ -446,7 +446,7 @@ def test_validate_backtest_data(default_conf, mocker, caplog, testdatadir) -> No strategy = StrategyResolver.load_strategy(default_conf) timerange = TimeRange('index', 'index', 200, 250) - data = strategy.tickerdata_to_dataframe( + data = strategy.ohlcvdata_to_dataframe( load_data( datadir=testdatadir, timeframe='5m', diff --git a/tests/edge/test_edge.py b/tests/edge/test_edge.py index 2a0d19128..c68ac477c 100644 --- a/tests/edge/test_edge.py +++ b/tests/edge/test_edge.py @@ -11,7 +11,7 @@ import pytest from pandas import DataFrame, to_datetime from freqtrade.exceptions import OperationalException -from freqtrade.data.converter import parse_ticker_dataframe +from freqtrade.data.converter import ohlcv_to_dataframe from freqtrade.edge import Edge, PairInfo from freqtrade.strategy.interface import SellType from tests.conftest import get_patched_freqtradebot, log_has @@ -26,7 +26,7 @@ from tests.optimize import (BTContainer, BTrade, _build_backtest_dataframe, # 5) Stoploss and sell are hit. should sell on stoploss #################################################################### -ticker_start_time = arrow.get(2018, 10, 3) +tests_start_time = arrow.get(2018, 10, 3) ticker_interval_in_minute = 60 _ohlc = {'date': 0, 'buy': 1, 'open': 2, 'high': 3, 'low': 4, 'close': 5, 'sell': 6, 'volume': 7} @@ -43,10 +43,10 @@ def _validate_ohlc(buy_ohlc_sell_matrice): def _build_dataframe(buy_ohlc_sell_matrice): _validate_ohlc(buy_ohlc_sell_matrice) - tickers = [] + data = [] for ohlc in buy_ohlc_sell_matrice: - ticker = { - 'date': ticker_start_time.shift( + d = { + 'date': tests_start_time.shift( minutes=( ohlc[0] * ticker_interval_in_minute)).timestamp * @@ -57,9 +57,9 @@ def _build_dataframe(buy_ohlc_sell_matrice): 'low': ohlc[4], 'close': ohlc[5], 'sell': ohlc[6]} - tickers.append(ticker) + data.append(d) - frame = DataFrame(tickers) + frame = DataFrame(data) frame['date'] = to_datetime(frame['date'], unit='ms', utc=True, @@ -69,7 +69,7 @@ def _build_dataframe(buy_ohlc_sell_matrice): def _time_on_candle(number): - return np.datetime64(ticker_start_time.shift( + return np.datetime64(tests_start_time.shift( minutes=(number * ticker_interval_in_minute)).timestamp * 1000, 'ms') @@ -262,7 +262,7 @@ def mocked_load_data(datadir, pairs=[], timeframe='0m', NEOBTC = [ [ - ticker_start_time.shift(minutes=(x * ticker_interval_in_minute)).timestamp * 1000, + tests_start_time.shift(minutes=(x * ticker_interval_in_minute)).timestamp * 1000, math.sin(x * hz) / 1000 + base, math.sin(x * hz) / 1000 + base + 0.0001, math.sin(x * hz) / 1000 + base - 0.0001, @@ -274,7 +274,7 @@ def mocked_load_data(datadir, pairs=[], timeframe='0m', base = 0.002 LTCBTC = [ [ - ticker_start_time.shift(minutes=(x * ticker_interval_in_minute)).timestamp * 1000, + tests_start_time.shift(minutes=(x * ticker_interval_in_minute)).timestamp * 1000, math.sin(x * hz) / 1000 + base, math.sin(x * hz) / 1000 + base + 0.0001, math.sin(x * hz) / 1000 + base - 0.0001, @@ -282,8 +282,10 @@ def mocked_load_data(datadir, pairs=[], timeframe='0m', 123.45 ] for x in range(0, 500)] - pairdata = {'NEO/BTC': parse_ticker_dataframe(NEOBTC, '1h', pair="NEO/BTC", fill_missing=True), - 'LTC/BTC': parse_ticker_dataframe(LTCBTC, '1h', pair="LTC/BTC", fill_missing=True)} + pairdata = {'NEO/BTC': ohlcv_to_dataframe(NEOBTC, '1h', pair="NEO/BTC", + fill_missing=True), + 'LTC/BTC': ohlcv_to_dataframe(LTCBTC, '1h', pair="LTC/BTC", + fill_missing=True)} return pairdata diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 6bec53d49..8d8930f66 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -581,7 +581,7 @@ def test_validate_timeframes_failed(default_conf, mocker): mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={})) mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock()) with pytest.raises(OperationalException, - match=r"Invalid ticker interval '3m'. This exchange supports.*"): + match=r"Invalid timeframe '3m'. This exchange supports.*"): Exchange(default_conf) default_conf["ticker_interval"] = "15s" @@ -1211,7 +1211,7 @@ def test_fetch_ticker(default_conf, mocker, exchange_name): @pytest.mark.parametrize("exchange_name", EXCHANGES) def test_get_historic_ohlcv(default_conf, mocker, caplog, exchange_name): exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) - tick = [ + ohlcv = [ [ arrow.utcnow().timestamp * 1000, # unix timestamp ms 1, # open @@ -1224,7 +1224,7 @@ def test_get_historic_ohlcv(default_conf, mocker, caplog, exchange_name): pair = 'ETH/BTC' async def mock_candle_hist(pair, timeframe, since_ms): - return pair, timeframe, tick + return pair, timeframe, ohlcv exchange._async_get_candle_history = Mock(wraps=mock_candle_hist) # one_call calculation * 1.8 should do 2 calls @@ -1232,12 +1232,12 @@ def test_get_historic_ohlcv(default_conf, mocker, caplog, exchange_name): ret = exchange.get_historic_ohlcv(pair, "5m", int((arrow.utcnow().timestamp - since) * 1000)) assert exchange._async_get_candle_history.call_count == 2 - # Returns twice the above tick + # Returns twice the above OHLCV data assert len(ret) == 2 def test_refresh_latest_ohlcv(mocker, default_conf, caplog) -> None: - tick = [ + ohlcv = [ [ (arrow.utcnow().timestamp - 1) * 1000, # unix timestamp ms 1, # open @@ -1258,14 +1258,14 @@ def test_refresh_latest_ohlcv(mocker, default_conf, caplog) -> None: caplog.set_level(logging.DEBUG) exchange = get_patched_exchange(mocker, default_conf) - exchange._api_async.fetch_ohlcv = get_mock_coro(tick) + exchange._api_async.fetch_ohlcv = get_mock_coro(ohlcv) pairs = [('IOTA/ETH', '5m'), ('XRP/ETH', '5m')] # empty dicts assert not exchange._klines exchange.refresh_latest_ohlcv(pairs) - assert log_has(f'Refreshing ohlcv data for {len(pairs)} pairs', caplog) + assert log_has(f'Refreshing candle (OHLCV) data for {len(pairs)} pairs', caplog) assert exchange._klines assert exchange._api_async.fetch_ohlcv.call_count == 2 for pair in pairs: @@ -1283,14 +1283,15 @@ def test_refresh_latest_ohlcv(mocker, default_conf, caplog) -> None: exchange.refresh_latest_ohlcv([('IOTA/ETH', '5m'), ('XRP/ETH', '5m')]) assert exchange._api_async.fetch_ohlcv.call_count == 2 - assert log_has(f"Using cached ohlcv data for pair {pairs[0][0]}, timeframe {pairs[0][1]} ...", + assert log_has(f"Using cached candle (OHLCV) data for pair {pairs[0][0]}, " + f"timeframe {pairs[0][1]} ...", caplog) @pytest.mark.asyncio @pytest.mark.parametrize("exchange_name", EXCHANGES) async def test__async_get_candle_history(default_conf, mocker, caplog, exchange_name): - tick = [ + ohlcv = [ [ arrow.utcnow().timestamp * 1000, # unix timestamp ms 1, # open @@ -1304,7 +1305,7 @@ async def test__async_get_candle_history(default_conf, mocker, caplog, exchange_ caplog.set_level(logging.DEBUG) exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) # Monkey-patch async function - exchange._api_async.fetch_ohlcv = get_mock_coro(tick) + exchange._api_async.fetch_ohlcv = get_mock_coro(ohlcv) pair = 'ETH/BTC' res = await exchange._async_get_candle_history(pair, "5m") @@ -1312,9 +1313,9 @@ async def test__async_get_candle_history(default_conf, mocker, caplog, exchange_ assert len(res) == 3 assert res[0] == pair assert res[1] == "5m" - assert res[2] == tick + assert res[2] == ohlcv assert exchange._api_async.fetch_ohlcv.call_count == 1 - assert not log_has(f"Using cached ohlcv data for {pair} ...", caplog) + assert not log_has(f"Using cached candle (OHLCV) data for {pair} ...", caplog) # exchange = Exchange(default_conf) await async_ccxt_exception(mocker, default_conf, MagicMock(), @@ -1322,14 +1323,15 @@ async def test__async_get_candle_history(default_conf, mocker, caplog, exchange_ pair='ABCD/BTC', timeframe=default_conf['ticker_interval']) api_mock = MagicMock() - with pytest.raises(OperationalException, match=r'Could not fetch ticker data*'): + with pytest.raises(OperationalException, + match=r'Could not fetch historical candle \(OHLCV\) data.*'): api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.BaseError("Unknown error")) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) await exchange._async_get_candle_history(pair, "5m", (arrow.utcnow().timestamp - 2000) * 1000) with pytest.raises(OperationalException, match=r'Exchange.* does not support fetching ' - r'historical candlestick data\..*'): + r'historical candle \(OHLCV\) data\..*'): api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.NotSupported("Not supported")) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) await exchange._async_get_candle_history(pair, "5m", @@ -1339,7 +1341,7 @@ async def test__async_get_candle_history(default_conf, mocker, caplog, exchange_ @pytest.mark.asyncio async def test__async_get_candle_history_empty(default_conf, mocker, caplog): """ Test empty exchange result """ - tick = [] + ohlcv = [] caplog.set_level(logging.DEBUG) exchange = get_patched_exchange(mocker, default_conf) @@ -1353,7 +1355,7 @@ async def test__async_get_candle_history_empty(default_conf, mocker, caplog): assert len(res) == 3 assert res[0] == pair assert res[1] == "5m" - assert res[2] == tick + assert res[2] == ohlcv assert exchange._api_async.fetch_ohlcv.call_count == 1 @@ -1431,8 +1433,8 @@ async def test___async_get_candle_history_sort(default_conf, mocker, exchange_na return sorted(data, key=key) # GDAX use-case (real data from GDAX) - # This ticker history is ordered DESC (newest first, oldest last) - tick = [ + # This OHLCV data is ordered DESC (newest first, oldest last) + ohlcv = [ [1527833100000, 0.07666, 0.07671, 0.07666, 0.07668, 16.65244264], [1527832800000, 0.07662, 0.07666, 0.07662, 0.07666, 1.30051526], [1527832500000, 0.07656, 0.07661, 0.07656, 0.07661, 12.034778840000001], @@ -1445,31 +1447,31 @@ async def test___async_get_candle_history_sort(default_conf, mocker, exchange_na [1527830400000, 0.07649, 0.07651, 0.07649, 0.07651, 2.5734867] ] exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) - exchange._api_async.fetch_ohlcv = get_mock_coro(tick) + exchange._api_async.fetch_ohlcv = get_mock_coro(ohlcv) sort_mock = mocker.patch('freqtrade.exchange.exchange.sorted', MagicMock(side_effect=sort_data)) - # Test the ticker history sort + # Test the OHLCV data sort res = await exchange._async_get_candle_history('ETH/BTC', default_conf['ticker_interval']) assert res[0] == 'ETH/BTC' - ticks = res[2] + res_ohlcv = res[2] assert sort_mock.call_count == 1 - assert ticks[0][0] == 1527830400000 - assert ticks[0][1] == 0.07649 - assert ticks[0][2] == 0.07651 - assert ticks[0][3] == 0.07649 - assert ticks[0][4] == 0.07651 - assert ticks[0][5] == 2.5734867 + assert res_ohlcv[0][0] == 1527830400000 + assert res_ohlcv[0][1] == 0.07649 + assert res_ohlcv[0][2] == 0.07651 + assert res_ohlcv[0][3] == 0.07649 + assert res_ohlcv[0][4] == 0.07651 + assert res_ohlcv[0][5] == 2.5734867 - assert ticks[9][0] == 1527833100000 - assert ticks[9][1] == 0.07666 - assert ticks[9][2] == 0.07671 - assert ticks[9][3] == 0.07666 - assert ticks[9][4] == 0.07668 - assert ticks[9][5] == 16.65244264 + assert res_ohlcv[9][0] == 1527833100000 + assert res_ohlcv[9][1] == 0.07666 + assert res_ohlcv[9][2] == 0.07671 + assert res_ohlcv[9][3] == 0.07666 + assert res_ohlcv[9][4] == 0.07668 + assert res_ohlcv[9][5] == 16.65244264 # Bittrex use-case (real data from Bittrex) - # This ticker history is ordered ASC (oldest first, newest last) - tick = [ + # This OHLCV data is ordered ASC (oldest first, newest last) + ohlcv = [ [1527827700000, 0.07659999, 0.0766, 0.07627, 0.07657998, 1.85216924], [1527828000000, 0.07657995, 0.07657995, 0.0763, 0.0763, 26.04051037], [1527828300000, 0.0763, 0.07659998, 0.0763, 0.0764, 10.36434124], @@ -1481,29 +1483,29 @@ async def test___async_get_candle_history_sort(default_conf, mocker, exchange_na [1527830100000, 0.076695, 0.07671, 0.07624171, 0.07671, 1.80689244], [1527830400000, 0.07671, 0.07674399, 0.07629216, 0.07655213, 2.31452783] ] - exchange._api_async.fetch_ohlcv = get_mock_coro(tick) + exchange._api_async.fetch_ohlcv = get_mock_coro(ohlcv) # Reset sort mock sort_mock = mocker.patch('freqtrade.exchange.sorted', MagicMock(side_effect=sort_data)) - # Test the ticker history sort + # Test the OHLCV data sort res = await exchange._async_get_candle_history('ETH/BTC', default_conf['ticker_interval']) assert res[0] == 'ETH/BTC' assert res[1] == default_conf['ticker_interval'] - ticks = res[2] + res_ohlcv = res[2] # Sorted not called again - data is already in order assert sort_mock.call_count == 0 - assert ticks[0][0] == 1527827700000 - assert ticks[0][1] == 0.07659999 - assert ticks[0][2] == 0.0766 - assert ticks[0][3] == 0.07627 - assert ticks[0][4] == 0.07657998 - assert ticks[0][5] == 1.85216924 + assert res_ohlcv[0][0] == 1527827700000 + assert res_ohlcv[0][1] == 0.07659999 + assert res_ohlcv[0][2] == 0.0766 + assert res_ohlcv[0][3] == 0.07627 + assert res_ohlcv[0][4] == 0.07657998 + assert res_ohlcv[0][5] == 1.85216924 - assert ticks[9][0] == 1527830400000 - assert ticks[9][1] == 0.07671 - assert ticks[9][2] == 0.07674399 - assert ticks[9][3] == 0.07629216 - assert ticks[9][4] == 0.07655213 - assert ticks[9][5] == 2.31452783 + assert res_ohlcv[9][0] == 1527830400000 + assert res_ohlcv[9][1] == 0.07671 + assert res_ohlcv[9][2] == 0.07674399 + assert res_ohlcv[9][3] == 0.07629216 + assert res_ohlcv[9][4] == 0.07655213 + assert res_ohlcv[9][5] == 2.31452783 @pytest.mark.asyncio diff --git a/tests/optimize/__init__.py b/tests/optimize/__init__.py index 13605a38c..8bc66f02c 100644 --- a/tests/optimize/__init__.py +++ b/tests/optimize/__init__.py @@ -6,7 +6,7 @@ from pandas import DataFrame from freqtrade.exchange import timeframe_to_minutes from freqtrade.strategy.interface import SellType -ticker_start_time = arrow.get(2018, 10, 3) +tests_start_time = arrow.get(2018, 10, 3) tests_timeframe = '1h' @@ -36,14 +36,14 @@ class BTContainer(NamedTuple): def _get_frame_time_from_offset(offset): - return ticker_start_time.shift(minutes=(offset * timeframe_to_minutes(tests_timeframe)) - ).datetime + minutes = offset * timeframe_to_minutes(tests_timeframe) + return tests_start_time.shift(minutes=minutes).datetime -def _build_backtest_dataframe(ticker_with_signals): +def _build_backtest_dataframe(data): columns = ['date', 'open', 'high', 'low', 'close', 'volume', 'buy', 'sell'] - frame = DataFrame.from_records(ticker_with_signals, columns=columns) + frame = DataFrame.from_records(data, columns=columns) frame['date'] = frame['date'].apply(_get_frame_time_from_offset) # Ensure floats are in place for column in ['open', 'high', 'low', 'close', 'volume']: diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index 96855dc9d..1b6e23ffa 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -84,7 +84,7 @@ def simple_backtest(config, contour, num_results, mocker, testdatadir) -> None: backtesting = Backtesting(config) data = load_data_test(contour, testdatadir) - processed = backtesting.strategy.tickerdata_to_dataframe(data) + processed = backtesting.strategy.ohlcvdata_to_dataframe(data) min_date, max_date = get_timerange(processed) assert isinstance(processed, dict) results = backtesting.backtest( @@ -105,7 +105,7 @@ def _make_backtest_conf(mocker, datadir, conf=None, pair='UNITTEST/BTC'): data = trim_dictlist(data, -201) patch_exchange(mocker) backtesting = Backtesting(conf) - processed = backtesting.strategy.tickerdata_to_dataframe(data) + processed = backtesting.strategy.ohlcvdata_to_dataframe(data) min_date, max_date = get_timerange(processed) return { 'processed': processed, @@ -275,7 +275,7 @@ def test_backtesting_init(mocker, default_conf, order_types) -> None: backtesting = Backtesting(default_conf) assert backtesting.config == default_conf assert backtesting.timeframe == '5m' - assert callable(backtesting.strategy.tickerdata_to_dataframe) + assert callable(backtesting.strategy.ohlcvdata_to_dataframe) assert callable(backtesting.strategy.advise_buy) assert callable(backtesting.strategy.advise_sell) assert isinstance(backtesting.strategy.dp, DataProvider) @@ -297,7 +297,7 @@ def test_backtesting_init_no_ticker_interval(mocker, default_conf, caplog) -> No "or as cli argument `--ticker-interval 5m`", caplog) -def test_tickerdata_with_fee(default_conf, mocker, testdatadir) -> None: +def test_data_with_fee(default_conf, mocker, testdatadir) -> None: patch_exchange(mocker) default_conf['fee'] = 0.1234 @@ -307,21 +307,21 @@ def test_tickerdata_with_fee(default_conf, mocker, testdatadir) -> None: assert fee_mock.call_count == 0 -def test_tickerdata_to_dataframe_bt(default_conf, mocker, testdatadir) -> None: +def test_data_to_dataframe_bt(default_conf, mocker, testdatadir) -> None: patch_exchange(mocker) timerange = TimeRange.parse_timerange('1510694220-1510700340') - tickerlist = history.load_data(testdatadir, '1m', ['UNITTEST/BTC'], timerange=timerange, - fill_up_missing=True) + data = history.load_data(testdatadir, '1m', ['UNITTEST/BTC'], timerange=timerange, + fill_up_missing=True) backtesting = Backtesting(default_conf) - data = backtesting.strategy.tickerdata_to_dataframe(tickerlist) - assert len(data['UNITTEST/BTC']) == 102 + processed = backtesting.strategy.ohlcvdata_to_dataframe(data) + assert len(processed['UNITTEST/BTC']) == 102 # Load strategy to compare the result between Backtesting function and strategy are the same default_conf.update({'strategy': 'DefaultStrategy'}) strategy = StrategyResolver.load_strategy(default_conf) - data2 = strategy.tickerdata_to_dataframe(tickerlist) - assert data['UNITTEST/BTC'].equals(data2['UNITTEST/BTC']) + processed2 = strategy.ohlcvdata_to_dataframe(data) + assert processed['UNITTEST/BTC'].equals(processed2['UNITTEST/BTC']) def test_backtesting_start(default_conf, mocker, testdatadir, caplog) -> None: @@ -329,7 +329,6 @@ def test_backtesting_start(default_conf, mocker, testdatadir, caplog) -> None: return Arrow(2017, 11, 14, 21, 17), Arrow(2017, 11, 14, 22, 59) mocker.patch('freqtrade.data.history.get_timerange', get_timerange) - mocker.patch('freqtrade.exchange.Exchange.refresh_latest_ohlcv', MagicMock()) patch_exchange(mocker) mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', MagicMock()) mocker.patch('freqtrade.optimize.backtesting.generate_text_table', MagicMock(return_value=1)) @@ -360,7 +359,6 @@ def test_backtesting_start_no_data(default_conf, mocker, caplog, testdatadir) -> mocker.patch('freqtrade.data.history.history_utils.load_pair_history', MagicMock(return_value=pd.DataFrame())) mocker.patch('freqtrade.data.history.get_timerange', get_timerange) - mocker.patch('freqtrade.exchange.Exchange.refresh_latest_ohlcv', MagicMock()) patch_exchange(mocker) mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', MagicMock()) mocker.patch('freqtrade.optimize.backtesting.generate_text_table', MagicMock(return_value=1)) @@ -385,10 +383,10 @@ def test_backtest(default_conf, fee, mocker, testdatadir) -> None: timerange = TimeRange('date', None, 1517227800, 0) data = history.load_data(datadir=testdatadir, timeframe='5m', pairs=['UNITTEST/BTC'], timerange=timerange) - data_processed = backtesting.strategy.tickerdata_to_dataframe(data) - min_date, max_date = get_timerange(data_processed) + processed = backtesting.strategy.ohlcvdata_to_dataframe(data) + min_date, max_date = get_timerange(processed) results = backtesting.backtest( - processed=data_processed, + processed=processed, stake_amount=default_conf['stake_amount'], start_date=min_date, end_date=max_date, @@ -416,7 +414,7 @@ def test_backtest(default_conf, fee, mocker, testdatadir) -> None: 'sell_reason': [SellType.ROI, SellType.ROI] }) pd.testing.assert_frame_equal(results, expected) - data_pair = data_processed[pair] + data_pair = processed[pair] for _, t in results.iterrows(): ln = data_pair.loc[data_pair["date"] == t["open_time"]] # Check open trade rate alignes to open rate @@ -439,7 +437,7 @@ def test_backtest_1min_ticker_interval(default_conf, fee, mocker, testdatadir) - timerange = TimeRange.parse_timerange('1510688220-1510700340') data = history.load_data(datadir=testdatadir, timeframe='1m', pairs=['UNITTEST/BTC'], timerange=timerange) - processed = backtesting.strategy.tickerdata_to_dataframe(data) + processed = backtesting.strategy.ohlcvdata_to_dataframe(data) min_date, max_date = get_timerange(processed) results = backtesting.backtest( processed=processed, @@ -458,7 +456,7 @@ def test_processed(default_conf, mocker, testdatadir) -> None: backtesting = Backtesting(default_conf) dict_of_tickerrows = load_data_test('raise', testdatadir) - dataframes = backtesting.strategy.tickerdata_to_dataframe(dict_of_tickerrows) + dataframes = backtesting.strategy.ohlcvdata_to_dataframe(dict_of_tickerrows) dataframe = dataframes['UNITTEST/BTC'] cols = dataframe.columns # assert the dataframe got some of the indicator columns @@ -557,10 +555,10 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir) backtesting.strategy.advise_buy = _trend_alternate_hold # Override backtesting.strategy.advise_sell = _trend_alternate_hold # Override - data_processed = backtesting.strategy.tickerdata_to_dataframe(data) - min_date, max_date = get_timerange(data_processed) + processed = backtesting.strategy.ohlcvdata_to_dataframe(data) + min_date, max_date = get_timerange(processed) backtest_conf = { - 'processed': data_processed, + 'processed': processed, 'stake_amount': default_conf['stake_amount'], 'start_date': min_date, 'end_date': max_date, @@ -576,7 +574,7 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir) assert len(evaluate_result_multi(results, '5m', 3)) == 0 backtest_conf = { - 'processed': data_processed, + 'processed': processed, 'stake_amount': default_conf['stake_amount'], 'start_date': min_date, 'end_date': max_date, diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index 0406157f6..eefc6b28a 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -524,7 +524,7 @@ def test_start_calls_optimizer(mocker, default_conf, caplog, capsys) -> None: }]) ) patch_exchange(mocker) - # Co-test loading ticker-interval from strategy + # Co-test loading timeframe from strategy del default_conf['ticker_interval'] default_conf.update({'config': 'config.json.example', 'hyperopt': 'DefaultHyperOpt', @@ -534,7 +534,7 @@ def test_start_calls_optimizer(mocker, default_conf, caplog, capsys) -> None: 'hyperopt_jobs': 1, }) hyperopt = Hyperopt(default_conf) - hyperopt.backtesting.strategy.tickerdata_to_dataframe = MagicMock() + hyperopt.backtesting.strategy.ohlcvdata_to_dataframe = MagicMock() hyperopt.custom_hyperopt.generate_roi_table = MagicMock(return_value={}) hyperopt.start() @@ -544,7 +544,7 @@ def test_start_calls_optimizer(mocker, default_conf, caplog, capsys) -> None: out, err = capsys.readouterr() assert 'Best result:\n\n* 1/1: foo result Objective: 1.00000\n' in out assert dumper.called - # Should be called twice, once for tickerdata, once to save evaluations + # Should be called twice, once for historical candle data, once to save evaluations assert dumper.call_count == 2 assert hasattr(hyperopt.backtesting.strategy, "advise_sell") assert hasattr(hyperopt.backtesting.strategy, "advise_buy") @@ -630,8 +630,8 @@ def test_has_space(hyperopt, spaces, expected_results): def test_populate_indicators(hyperopt, testdatadir) -> None: - tickerlist = load_data(testdatadir, '1m', ['UNITTEST/BTC'], fill_up_missing=True) - dataframes = hyperopt.backtesting.strategy.tickerdata_to_dataframe(tickerlist) + data = load_data(testdatadir, '1m', ['UNITTEST/BTC'], fill_up_missing=True) + dataframes = hyperopt.backtesting.strategy.ohlcvdata_to_dataframe(data) dataframe = hyperopt.custom_hyperopt.populate_indicators(dataframes['UNITTEST/BTC'], {'pair': 'UNITTEST/BTC'}) @@ -642,8 +642,8 @@ def test_populate_indicators(hyperopt, testdatadir) -> None: def test_buy_strategy_generator(hyperopt, testdatadir) -> None: - tickerlist = load_data(testdatadir, '1m', ['UNITTEST/BTC'], fill_up_missing=True) - dataframes = hyperopt.backtesting.strategy.tickerdata_to_dataframe(tickerlist) + data = load_data(testdatadir, '1m', ['UNITTEST/BTC'], fill_up_missing=True) + dataframes = hyperopt.backtesting.strategy.ohlcvdata_to_dataframe(data) dataframe = hyperopt.custom_hyperopt.populate_indicators(dataframes['UNITTEST/BTC'], {'pair': 'UNITTEST/BTC'}) @@ -783,7 +783,7 @@ def test_clean_hyperopt(mocker, default_conf, caplog): h = Hyperopt(default_conf) assert unlinkmock.call_count == 2 - assert log_has(f"Removing `{h.tickerdata_pickle}`.", caplog) + assert log_has(f"Removing `{h.data_pickle_file}`.", caplog) def test_continue_hyperopt(mocker, default_conf, caplog): @@ -845,7 +845,7 @@ def test_print_json_spaces_all(mocker, default_conf, caplog, capsys) -> None: }) hyperopt = Hyperopt(default_conf) - hyperopt.backtesting.strategy.tickerdata_to_dataframe = MagicMock() + hyperopt.backtesting.strategy.ohlcvdata_to_dataframe = MagicMock() hyperopt.custom_hyperopt.generate_roi_table = MagicMock(return_value={}) hyperopt.start() @@ -859,7 +859,7 @@ def test_print_json_spaces_all(mocker, default_conf, caplog, capsys) -> None: ) assert result_str in out # noqa: E501 assert dumper.called - # Should be called twice, once for tickerdata, once to save evaluations + # Should be called twice, once for historical candle data, once to save evaluations assert dumper.call_count == 2 @@ -903,7 +903,7 @@ def test_print_json_spaces_default(mocker, default_conf, caplog, capsys) -> None }) hyperopt = Hyperopt(default_conf) - hyperopt.backtesting.strategy.tickerdata_to_dataframe = MagicMock() + hyperopt.backtesting.strategy.ohlcvdata_to_dataframe = MagicMock() hyperopt.custom_hyperopt.generate_roi_table = MagicMock(return_value={}) hyperopt.start() @@ -913,7 +913,7 @@ def test_print_json_spaces_default(mocker, default_conf, caplog, capsys) -> None out, err = capsys.readouterr() assert '{"params":{"mfi-value":null,"sell-mfi-value":null},"minimal_roi":{},"stoploss":null}' in out # noqa: E501 assert dumper.called - # Should be called twice, once for tickerdata, once to save evaluations + # Should be called twice, once for historical candle data, once to save evaluations assert dumper.call_count == 2 @@ -953,7 +953,7 @@ def test_print_json_spaces_roi_stoploss(mocker, default_conf, caplog, capsys) -> }) hyperopt = Hyperopt(default_conf) - hyperopt.backtesting.strategy.tickerdata_to_dataframe = MagicMock() + hyperopt.backtesting.strategy.ohlcvdata_to_dataframe = MagicMock() hyperopt.custom_hyperopt.generate_roi_table = MagicMock(return_value={}) hyperopt.start() @@ -963,7 +963,7 @@ def test_print_json_spaces_roi_stoploss(mocker, default_conf, caplog, capsys) -> out, err = capsys.readouterr() assert '{"minimal_roi":{},"stoploss":null}' in out assert dumper.called - # Should be called twice, once for tickerdata, once to save evaluations + # Should be called twice, once for historical candle data, once to save evaluations assert dumper.call_count == 2 @@ -1000,7 +1000,7 @@ def test_simplified_interface_roi_stoploss(mocker, default_conf, caplog, capsys) 'hyperopt_jobs': 1, }) hyperopt = Hyperopt(default_conf) - hyperopt.backtesting.strategy.tickerdata_to_dataframe = MagicMock() + hyperopt.backtesting.strategy.ohlcvdata_to_dataframe = MagicMock() hyperopt.custom_hyperopt.generate_roi_table = MagicMock(return_value={}) del hyperopt.custom_hyperopt.__class__.buy_strategy_generator @@ -1015,7 +1015,7 @@ def test_simplified_interface_roi_stoploss(mocker, default_conf, caplog, capsys) out, err = capsys.readouterr() assert 'Best result:\n\n* 1/1: foo result Objective: 1.00000\n' in out assert dumper.called - # Should be called twice, once for tickerdata, once to save evaluations + # Should be called twice, once for historical candle data, once to save evaluations assert dumper.call_count == 2 assert hasattr(hyperopt.backtesting.strategy, "advise_sell") assert hasattr(hyperopt.backtesting.strategy, "advise_buy") @@ -1043,7 +1043,7 @@ def test_simplified_interface_all_failed(mocker, default_conf, caplog, capsys) - 'hyperopt_jobs': 1, }) hyperopt = Hyperopt(default_conf) - hyperopt.backtesting.strategy.tickerdata_to_dataframe = MagicMock() + hyperopt.backtesting.strategy.ohlcvdata_to_dataframe = MagicMock() hyperopt.custom_hyperopt.generate_roi_table = MagicMock(return_value={}) del hyperopt.custom_hyperopt.__class__.buy_strategy_generator @@ -1088,7 +1088,7 @@ def test_simplified_interface_buy(mocker, default_conf, caplog, capsys) -> None: 'hyperopt_jobs': 1, }) hyperopt = Hyperopt(default_conf) - hyperopt.backtesting.strategy.tickerdata_to_dataframe = MagicMock() + hyperopt.backtesting.strategy.ohlcvdata_to_dataframe = MagicMock() hyperopt.custom_hyperopt.generate_roi_table = MagicMock(return_value={}) # TODO: sell_strategy_generator() is actually not called because @@ -1103,7 +1103,7 @@ def test_simplified_interface_buy(mocker, default_conf, caplog, capsys) -> None: out, err = capsys.readouterr() assert 'Best result:\n\n* 1/1: foo result Objective: 1.00000\n' in out assert dumper.called - # Should be called twice, once for tickerdata, once to save evaluations + # Should be called twice, once for historical candle data, once to save evaluations assert dumper.call_count == 2 assert hasattr(hyperopt.backtesting.strategy, "advise_sell") assert hasattr(hyperopt.backtesting.strategy, "advise_buy") @@ -1145,7 +1145,7 @@ def test_simplified_interface_sell(mocker, default_conf, caplog, capsys) -> None 'hyperopt_jobs': 1, }) hyperopt = Hyperopt(default_conf) - hyperopt.backtesting.strategy.tickerdata_to_dataframe = MagicMock() + hyperopt.backtesting.strategy.ohlcvdata_to_dataframe = MagicMock() hyperopt.custom_hyperopt.generate_roi_table = MagicMock(return_value={}) # TODO: buy_strategy_generator() is actually not called because @@ -1160,7 +1160,7 @@ def test_simplified_interface_sell(mocker, default_conf, caplog, capsys) -> None out, err = capsys.readouterr() assert 'Best result:\n\n* 1/1: foo result Objective: 1.00000\n' in out assert dumper.called - # Should be called twice, once for tickerdata, once to save evaluations + # Should be called twice, once for historical candle data, once to save evaluations assert dumper.call_count == 2 assert hasattr(hyperopt.backtesting.strategy, "advise_sell") assert hasattr(hyperopt.backtesting.strategy, "advise_buy") @@ -1194,7 +1194,7 @@ def test_simplified_interface_failed(mocker, default_conf, caplog, capsys, metho 'hyperopt_jobs': 1, }) hyperopt = Hyperopt(default_conf) - hyperopt.backtesting.strategy.tickerdata_to_dataframe = MagicMock() + hyperopt.backtesting.strategy.ohlcvdata_to_dataframe = MagicMock() hyperopt.custom_hyperopt.generate_roi_table = MagicMock(return_value={}) delattr(hyperopt.custom_hyperopt.__class__, method) diff --git a/tests/strategy/strats/default_strategy.py b/tests/strategy/strats/default_strategy.py index 6c343b477..7ea55d3f9 100644 --- a/tests/strategy/strats/default_strategy.py +++ b/tests/strategy/strats/default_strategy.py @@ -68,7 +68,7 @@ class DefaultStrategy(IStrategy): Performance Note: For the best performance be frugal on the number of indicators you are using. Let uncomment only the indicator you are using in your strategies or your hyperopt configuration, otherwise you will waste your memory and CPU usage. - :param dataframe: Raw data from the exchange and parsed by parse_ticker_dataframe() + :param dataframe: Dataframe with data from the exchange :param metadata: Additional information, like the currently traded pair :return: a Dataframe with all mandatory indicators for the strategies """ diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index 86d0738c6..949dda4a0 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -17,69 +17,69 @@ from tests.conftest import get_patched_exchange, log_has _STRATEGY = DefaultStrategy(config={}) -def test_returns_latest_buy_signal(mocker, default_conf, ticker_history): +def test_returns_latest_buy_signal(mocker, default_conf, ohlcv_history): mocker.patch.object( _STRATEGY, '_analyze_ticker_internal', return_value=DataFrame([{'buy': 1, 'sell': 0, 'date': arrow.utcnow()}]) ) - assert _STRATEGY.get_signal('ETH/BTC', '5m', ticker_history) == (True, False) + assert _STRATEGY.get_signal('ETH/BTC', '5m', ohlcv_history) == (True, False) mocker.patch.object( _STRATEGY, '_analyze_ticker_internal', return_value=DataFrame([{'buy': 0, 'sell': 1, 'date': arrow.utcnow()}]) ) - assert _STRATEGY.get_signal('ETH/BTC', '5m', ticker_history) == (False, True) + assert _STRATEGY.get_signal('ETH/BTC', '5m', ohlcv_history) == (False, True) -def test_returns_latest_sell_signal(mocker, default_conf, ticker_history): +def test_returns_latest_sell_signal(mocker, default_conf, ohlcv_history): mocker.patch.object( _STRATEGY, '_analyze_ticker_internal', return_value=DataFrame([{'sell': 1, 'buy': 0, 'date': arrow.utcnow()}]) ) - assert _STRATEGY.get_signal('ETH/BTC', '5m', ticker_history) == (False, True) + assert _STRATEGY.get_signal('ETH/BTC', '5m', ohlcv_history) == (False, True) mocker.patch.object( _STRATEGY, '_analyze_ticker_internal', return_value=DataFrame([{'sell': 0, 'buy': 1, 'date': arrow.utcnow()}]) ) - assert _STRATEGY.get_signal('ETH/BTC', '5m', ticker_history) == (True, False) + assert _STRATEGY.get_signal('ETH/BTC', '5m', ohlcv_history) == (True, False) def test_get_signal_empty(default_conf, mocker, caplog): assert (False, False) == _STRATEGY.get_signal('foo', default_conf['ticker_interval'], DataFrame()) - assert log_has('Empty ticker history for pair foo', caplog) + assert log_has('Empty candle (OHLCV) data for pair foo', caplog) caplog.clear() assert (False, False) == _STRATEGY.get_signal('bar', default_conf['ticker_interval'], []) - assert log_has('Empty ticker history for pair bar', caplog) + assert log_has('Empty candle (OHLCV) data for pair bar', caplog) -def test_get_signal_exception_valueerror(default_conf, mocker, caplog, ticker_history): +def test_get_signal_exception_valueerror(default_conf, mocker, caplog, ohlcv_history): caplog.set_level(logging.INFO) mocker.patch.object( _STRATEGY, '_analyze_ticker_internal', side_effect=ValueError('xyz') ) assert (False, False) == _STRATEGY.get_signal('foo', default_conf['ticker_interval'], - ticker_history) - assert log_has('Unable to analyze ticker for pair foo: xyz', caplog) + ohlcv_history) + assert log_has('Unable to analyze candle (OHLCV) data for pair foo: xyz', caplog) -def test_get_signal_empty_dataframe(default_conf, mocker, caplog, ticker_history): +def test_get_signal_empty_dataframe(default_conf, mocker, caplog, ohlcv_history): caplog.set_level(logging.INFO) mocker.patch.object( _STRATEGY, '_analyze_ticker_internal', return_value=DataFrame([]) ) assert (False, False) == _STRATEGY.get_signal('xyz', default_conf['ticker_interval'], - ticker_history) + ohlcv_history) assert log_has('Empty dataframe for pair xyz', caplog) -def test_get_signal_old_dataframe(default_conf, mocker, caplog, ticker_history): +def test_get_signal_old_dataframe(default_conf, mocker, caplog, ohlcv_history): caplog.set_level(logging.INFO) # default_conf defines a 5m interval. we check interval * 2 + 5m # this is necessary as the last candle is removed (partial candles) by default @@ -90,7 +90,7 @@ def test_get_signal_old_dataframe(default_conf, mocker, caplog, ticker_history): return_value=DataFrame(ticks) ) assert (False, False) == _STRATEGY.get_signal('xyz', default_conf['ticker_interval'], - ticker_history) + ohlcv_history) assert log_has('Outdated history for pair xyz. Last tick is 16 minutes old', caplog) @@ -103,15 +103,15 @@ def test_get_signal_handles_exceptions(mocker, default_conf): assert _STRATEGY.get_signal(exchange, 'ETH/BTC', '5m') == (False, False) -def test_tickerdata_to_dataframe(default_conf, testdatadir) -> None: +def test_ohlcvdata_to_dataframe(default_conf, testdatadir) -> None: default_conf.update({'strategy': 'DefaultStrategy'}) strategy = StrategyResolver.load_strategy(default_conf) timerange = TimeRange.parse_timerange('1510694220-1510700340') - tickerlist = load_data(testdatadir, '1m', ['UNITTEST/BTC'], timerange=timerange, - fill_up_missing=True) - data = strategy.tickerdata_to_dataframe(tickerlist) - assert len(data['UNITTEST/BTC']) == 102 # partial candle was removed + data = load_data(testdatadir, '1m', ['UNITTEST/BTC'], timerange=timerange, + fill_up_missing=True) + processed = strategy.ohlcvdata_to_dataframe(data) + assert len(processed['UNITTEST/BTC']) == 102 # partial candle was removed def test_min_roi_reached(default_conf, fee) -> None: @@ -222,7 +222,7 @@ def test_min_roi_reached3(default_conf, fee) -> None: assert strategy.min_roi_reached(trade, 0.31, arrow.utcnow().shift(minutes=-2).datetime) -def test_analyze_ticker_default(ticker_history, mocker, caplog) -> None: +def test_analyze_ticker_default(ohlcv_history, mocker, caplog) -> None: caplog.set_level(logging.DEBUG) ind_mock = MagicMock(side_effect=lambda x, meta: x) buy_mock = MagicMock(side_effect=lambda x, meta: x) @@ -235,7 +235,7 @@ def test_analyze_ticker_default(ticker_history, mocker, caplog) -> None: ) strategy = DefaultStrategy({}) - strategy.analyze_ticker(ticker_history, {'pair': 'ETH/BTC'}) + strategy.analyze_ticker(ohlcv_history, {'pair': 'ETH/BTC'}) assert ind_mock.call_count == 1 assert buy_mock.call_count == 1 assert buy_mock.call_count == 1 @@ -244,7 +244,7 @@ def test_analyze_ticker_default(ticker_history, mocker, caplog) -> None: assert not log_has('Skipping TA Analysis for already analyzed candle', caplog) caplog.clear() - strategy.analyze_ticker(ticker_history, {'pair': 'ETH/BTC'}) + strategy.analyze_ticker(ohlcv_history, {'pair': 'ETH/BTC'}) # No analysis happens as process_only_new_candles is true assert ind_mock.call_count == 2 assert buy_mock.call_count == 2 @@ -253,7 +253,7 @@ def test_analyze_ticker_default(ticker_history, mocker, caplog) -> None: assert not log_has('Skipping TA Analysis for already analyzed candle', caplog) -def test__analyze_ticker_internal_skip_analyze(ticker_history, mocker, caplog) -> None: +def test__analyze_ticker_internal_skip_analyze(ohlcv_history, mocker, caplog) -> None: caplog.set_level(logging.DEBUG) ind_mock = MagicMock(side_effect=lambda x, meta: x) buy_mock = MagicMock(side_effect=lambda x, meta: x) @@ -268,7 +268,7 @@ def test__analyze_ticker_internal_skip_analyze(ticker_history, mocker, caplog) - strategy = DefaultStrategy({}) strategy.process_only_new_candles = True - ret = strategy._analyze_ticker_internal(ticker_history, {'pair': 'ETH/BTC'}) + ret = strategy._analyze_ticker_internal(ohlcv_history, {'pair': 'ETH/BTC'}) assert 'high' in ret.columns assert 'low' in ret.columns assert 'close' in ret.columns @@ -280,7 +280,7 @@ def test__analyze_ticker_internal_skip_analyze(ticker_history, mocker, caplog) - assert not log_has('Skipping TA Analysis for already analyzed candle', caplog) caplog.clear() - ret = strategy._analyze_ticker_internal(ticker_history, {'pair': 'ETH/BTC'}) + ret = strategy._analyze_ticker_internal(ohlcv_history, {'pair': 'ETH/BTC'}) # No analysis happens as process_only_new_candles is true assert ind_mock.call_count == 1 assert buy_mock.call_count == 1 diff --git a/tests/test_misc.py b/tests/test_misc.py index 83e008466..c1e23926b 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -6,7 +6,7 @@ from unittest.mock import MagicMock import pytest -from freqtrade.data.converter import parse_ticker_dataframe +from freqtrade.data.converter import ohlcv_to_dataframe from freqtrade.misc import (datesarray_to_datetimearray, file_dump_json, file_load_json, format_ms_time, pair_to_filename, plural, shorten_date) @@ -18,9 +18,9 @@ def test_shorten_date() -> None: assert shorten_date(str_data) == str_shorten_data -def test_datesarray_to_datetimearray(ticker_history_list): - dataframes = parse_ticker_dataframe(ticker_history_list, "5m", pair="UNITTEST/BTC", - fill_missing=True) +def test_datesarray_to_datetimearray(ohlcv_history_list): + dataframes = ohlcv_to_dataframe(ohlcv_history_list, "5m", pair="UNITTEST/BTC", + fill_missing=True) dates = datesarray_to_datetimearray(dataframes['date']) assert isinstance(dates[0], datetime.datetime) diff --git a/tests/test_plotting.py b/tests/test_plotting.py index dd04035b7..42010ad0d 100644 --- a/tests/test_plotting.py +++ b/tests/test_plotting.py @@ -51,15 +51,15 @@ def test_init_plotscript(default_conf, mocker, testdatadir): default_conf["datadir"] = testdatadir default_conf['exportfilename'] = str(testdatadir / "backtest-result_test.json") ret = init_plotscript(default_conf) - assert "tickers" in ret + assert "ohlcv" in ret assert "trades" in ret assert "pairs" in ret default_conf['pairs'] = ["TRX/BTC", "ADA/BTC"] ret = init_plotscript(default_conf) - assert "tickers" in ret - assert "TRX/BTC" in ret["tickers"] - assert "ADA/BTC" in ret["tickers"] + assert "ohlcv" in ret + assert "TRX/BTC" in ret["ohlcv"] + assert "ADA/BTC" in ret["ohlcv"] def test_add_indicators(default_conf, testdatadir, caplog): @@ -269,14 +269,14 @@ def test_generate_profit_graph(testdatadir): pairs = ["TRX/BTC", "ADA/BTC"] trades = trades[trades['close_time'] < pd.Timestamp('2018-01-12', tz='UTC')] - tickers = history.load_data(datadir=testdatadir, - pairs=pairs, - timeframe='5m', - timerange=timerange - ) + data = history.load_data(datadir=testdatadir, + pairs=pairs, + timeframe='5m', + timerange=timerange) + trades = trades[trades['pair'].isin(pairs)] - fig = generate_profit_graph(pairs, tickers, trades, timeframe="5m") + fig = generate_profit_graph(pairs, data, trades, timeframe="5m") assert isinstance(fig, go.Figure) assert fig.layout.title.text == "Freqtrade Profit plot" From 4ad93ed6bbe2f9f5b653552d05f7d9b73fb9a30c Mon Sep 17 00:00:00 2001 From: Fredrik81 Date: Sun, 8 Mar 2020 22:41:05 +0100 Subject: [PATCH 124/202] Changed output for null columns --- freqtrade/optimize/hyperopt.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index d397c720c..f0c992760 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -420,19 +420,19 @@ class Hyperopt: trials['Trades'] = trials['Trades'].astype(str) trials['Total profit'] = trials['Total profit'].apply( - lambda x: '{:,.8f}'.format(x) if x != 0.0 else "--" + lambda x: '{:,.8f}'.format(x) if x != 0.0 else "" ) trials['Profit'] = trials['Profit'].apply( - lambda x: '{:,.2f}'.format(x) if not isna(x) else "--" + lambda x: '{:,.2f}'.format(x) if not isna(x) else "" ) trials['Avg profit'] = trials['Avg profit'].apply( - lambda x: ('{:,.2f}%'.format(x)) if not isna(x) else "--" + lambda x: ('{:,.2f}%'.format(x)) if not isna(x) else "" ) trials['Avg duration'] = trials['Avg duration'].apply( - lambda x: ('{:,.1f} m'.format(x)) if not isna(x) else "--" + lambda x: ('{:,.1f} m'.format(x)) if not isna(x) else "" ) trials['Objective'] = trials['Objective'].apply( - lambda x: '{:,.5f}'.format(x) if x != 100000 else "N/A" + lambda x: '{:,.5f}'.format(x) if x != 100000 else "" ) trials = trials.drop(columns=['is_initial_point', 'is_best', 'is_profit']) From cb419614cd10ae7e4fb063e4bc2b28c795a0ce31 Mon Sep 17 00:00:00 2001 From: Fredrik81 Date: Sun, 8 Mar 2020 23:00:21 +0100 Subject: [PATCH 125/202] Spelling miss --- freqtrade/optimize/hyperopt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index f0c992760..d12e9cd45 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -392,7 +392,7 @@ class Hyperopt: if not results: return - # Verification for owerwrite + # Verification for overwrite if not overwrite and path.isfile(csv_file): logging.error("CSV-File already exists and no overwrite specified!") return From c7b2f173ebcade7092be49f4d2aa87ec19745768 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 9 Mar 2020 08:32:42 +0000 Subject: [PATCH 126/202] Bump wrapt from 1.12.0 to 1.12.1 Bumps [wrapt](https://github.com/GrahamDumpleton/wrapt) from 1.12.0 to 1.12.1. - [Release notes](https://github.com/GrahamDumpleton/wrapt/releases) - [Changelog](https://github.com/GrahamDumpleton/wrapt/blob/develop/docs/changes.rst) - [Commits](https://github.com/GrahamDumpleton/wrapt/compare/1.12.0...1.12.1) Signed-off-by: dependabot-preview[bot] --- requirements-common.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-common.txt b/requirements-common.txt index a844e81bc..e44881041 100644 --- a/requirements-common.txt +++ b/requirements-common.txt @@ -7,7 +7,7 @@ arrow==0.15.5 cachetools==4.0.0 requests==2.23.0 urllib3==1.25.8 -wrapt==1.12.0 +wrapt==1.12.1 jsonschema==3.2.0 TA-Lib==0.4.17 tabulate==0.8.6 From 09c25faa51a68ab7c0e42795bc809b0da135782a Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 9 Mar 2020 08:33:37 +0000 Subject: [PATCH 127/202] Bump ccxt from 1.23.30 to 1.23.81 Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.23.30 to 1.23.81. - [Release notes](https://github.com/ccxt/ccxt/releases) - [Changelog](https://github.com/ccxt/ccxt/blob/master/CHANGELOG.md) - [Commits](https://github.com/ccxt/ccxt/compare/1.23.30...1.23.81) Signed-off-by: dependabot-preview[bot] --- requirements-common.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-common.txt b/requirements-common.txt index a844e81bc..f64644db9 100644 --- a/requirements-common.txt +++ b/requirements-common.txt @@ -1,6 +1,6 @@ # requirements without requirements installable via conda # mainly used for Raspberry pi installs -ccxt==1.23.30 +ccxt==1.23.81 SQLAlchemy==1.3.13 python-telegram-bot==12.4.2 arrow==0.15.5 From 46763e148b6ccec222d849467f0cabb60f900f7e Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 9 Mar 2020 08:33:59 +0000 Subject: [PATCH 128/202] Bump prompt-toolkit from 3.0.3 to 3.0.4 Bumps [prompt-toolkit](https://github.com/prompt-toolkit/python-prompt-toolkit) from 3.0.3 to 3.0.4. - [Release notes](https://github.com/prompt-toolkit/python-prompt-toolkit/releases) - [Changelog](https://github.com/prompt-toolkit/python-prompt-toolkit/blob/master/CHANGELOG) - [Commits](https://github.com/prompt-toolkit/python-prompt-toolkit/compare/3.0.3...3.0.4) Signed-off-by: dependabot-preview[bot] --- requirements-common.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-common.txt b/requirements-common.txt index a844e81bc..ec12f1c65 100644 --- a/requirements-common.txt +++ b/requirements-common.txt @@ -30,4 +30,4 @@ flask==1.1.1 colorama==0.4.3 # Building config files interactively questionary==1.5.1 -prompt-toolkit==3.0.3 +prompt-toolkit==3.0.4 From 23127b8da08ab45641a955dc94b58bfec582d38d Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 9 Mar 2020 08:34:33 +0000 Subject: [PATCH 129/202] Bump scikit-learn from 0.22.2 to 0.22.2.post1 Bumps [scikit-learn](https://github.com/scikit-learn/scikit-learn) from 0.22.2 to 0.22.2.post1. - [Release notes](https://github.com/scikit-learn/scikit-learn/releases) - [Commits](https://github.com/scikit-learn/scikit-learn/compare/0.22.2...0.22.2.post1) Signed-off-by: dependabot-preview[bot] --- requirements-hyperopt.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-hyperopt.txt b/requirements-hyperopt.txt index c713317ec..c7e586a33 100644 --- a/requirements-hyperopt.txt +++ b/requirements-hyperopt.txt @@ -3,7 +3,7 @@ # Required for hyperopt scipy==1.4.1 -scikit-learn==0.22.2 +scikit-learn==0.22.2.post1 scikit-optimize==0.7.4 filelock==3.0.12 joblib==0.14.1 From 4cc0d3dbc42600136a7539e7f523a31f375683c9 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 9 Mar 2020 08:35:16 +0000 Subject: [PATCH 130/202] Bump plotly from 4.5.2 to 4.5.3 Bumps [plotly](https://github.com/plotly/plotly.py) from 4.5.2 to 4.5.3. - [Release notes](https://github.com/plotly/plotly.py/releases) - [Changelog](https://github.com/plotly/plotly.py/blob/master/CHANGELOG.md) - [Commits](https://github.com/plotly/plotly.py/compare/v4.5.2...v4.5.3) Signed-off-by: dependabot-preview[bot] --- requirements-plot.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-plot.txt b/requirements-plot.txt index a70c3e0cf..381334a66 100644 --- a/requirements-plot.txt +++ b/requirements-plot.txt @@ -1,5 +1,5 @@ # Include all requirements to run the bot. -r requirements.txt -plotly==4.5.2 +plotly==4.5.3 From 5cbf325fda4f0a3d9bb6032122aa56d085118d83 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 9 Mar 2020 11:30:13 +0100 Subject: [PATCH 131/202] Allow different loglevels for message --- freqtrade/pairlist/IPairList.py | 24 ++++++++++++++++++++---- freqtrade/pairlist/VolumePairList.py | 2 +- freqtrade/pairlist/pairlistmanager.py | 2 +- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/freqtrade/pairlist/IPairList.py b/freqtrade/pairlist/IPairList.py index d45a329dd..f591fa8da 100644 --- a/freqtrade/pairlist/IPairList.py +++ b/freqtrade/pairlist/IPairList.py @@ -67,21 +67,37 @@ class IPairList(ABC): """ @staticmethod - def verify_blacklist(pairlist: List[str], blacklist: List[str]) -> List[str]: + def verify_blacklist(pairlist: List[str], blacklist: List[str], + aswarning: bool) -> List[str]: """ Verify and remove items from pairlist - returning a filtered pairlist. + Logs a warning or info depending on `aswarning`. + Pairlists explicitly using this method shall use `aswarning=False`! + :param pairlist: Pairlist to validate + :param blacklist: Blacklist to validate pairlist against + :param aswarning: Log message as Warning or info + :return: pairlist - blacklisted pairs """ for pair in deepcopy(pairlist): if pair in blacklist: - logger.warning(f"Pair {pair} in your blacklist. Removing it from whitelist...") + if aswarning: + logger.warning(f"Pair {pair} in your blacklist. Removing it from whitelist...") + else: + logger.info(f"Pair {pair} in your blacklist. Removing it from whitelist...") pairlist.remove(pair) return pairlist - def _verify_blacklist(self, pairlist: List[str]) -> List[str]: + def _verify_blacklist(self, pairlist: List[str], aswarning: bool = True) -> List[str]: """ Proxy method to verify_blacklist for easy access for child classes. + Logs a warning or info depending on `aswarning`. + Pairlists explicitly using this method shall use aswarning=False! + :param pairlist: Pairlist to validate + :param aswarning: Log message as Warning or info. + :return: pairlist - blacklisted pairs """ - return IPairList.verify_blacklist(pairlist, self._pairlistmanager.blacklist) + return IPairList.verify_blacklist(pairlist, self._pairlistmanager.blacklist, + aswarning=aswarning) def _whitelist_for_active_markets(self, pairlist: List[str]) -> List[str]: """ diff --git a/freqtrade/pairlist/VolumePairList.py b/freqtrade/pairlist/VolumePairList.py index d067d5e8a..9ce2adc9e 100644 --- a/freqtrade/pairlist/VolumePairList.py +++ b/freqtrade/pairlist/VolumePairList.py @@ -106,7 +106,7 @@ class VolumePairList(IPairList): # Validate whitelist to only have active market pairs pairs = self._whitelist_for_active_markets([s['symbol'] for s in sorted_tickers]) - pairs = self._verify_blacklist(pairs) + pairs = self._verify_blacklist(pairs, aswarning=False) # Limit to X number of pairs pairs = pairs[:self._number_pairs] logger.info(f"Searching {self._number_pairs} pairs: {pairs}") diff --git a/freqtrade/pairlist/pairlistmanager.py b/freqtrade/pairlist/pairlistmanager.py index 55828c6ef..5b4c5b602 100644 --- a/freqtrade/pairlist/pairlistmanager.py +++ b/freqtrade/pairlist/pairlistmanager.py @@ -91,6 +91,6 @@ class PairListManager(): pairlist = pl.filter_pairlist(pairlist, tickers) # Validation against blacklist happens after the pairlists to ensure blacklist is respected. - pairlist = IPairList.verify_blacklist(pairlist, self.blacklist) + pairlist = IPairList.verify_blacklist(pairlist, self.blacklist, True) self._whitelist = pairlist From c049651784491f82807a07cbc2b3d3d6a8f57e22 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 9 Mar 2020 11:30:28 +0100 Subject: [PATCH 132/202] whitelist_for_active_markets should not remove blacklisted items --- freqtrade/pairlist/IPairList.py | 1 - tests/pairlist/test_pairlist.py | 2 -- 2 files changed, 3 deletions(-) diff --git a/freqtrade/pairlist/IPairList.py b/freqtrade/pairlist/IPairList.py index f591fa8da..35844a99e 100644 --- a/freqtrade/pairlist/IPairList.py +++ b/freqtrade/pairlist/IPairList.py @@ -129,6 +129,5 @@ class IPairList(ABC): if pair not in sanitized_whitelist: sanitized_whitelist.append(pair) - sanitized_whitelist = self._verify_blacklist(sanitized_whitelist) # We need to remove pairs that are unknown return sanitized_whitelist diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index b8a4be037..1ce1151b7 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -240,8 +240,6 @@ def test_pairlist_class(mocker, whitelist_conf, markets, pairlist): (['ETH/BTC', 'TKN/BTC', 'ETH/USDT'], "is not compatible with your stake currency"), # BCH/BTC not available (['ETH/BTC', 'TKN/BTC', 'BCH/BTC'], "is not compatible with exchange"), - # BLK/BTC in blacklist - (['ETH/BTC', 'TKN/BTC', 'BLK/BTC'], "in your blacklist. Removing "), # BTT/BTC is inactive (['ETH/BTC', 'TKN/BTC', 'BTT/BTC'], "Market is not active") ]) From 856ba203d959716d40a5f0bab3569e63b807d76d Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 9 Mar 2020 15:04:28 +0100 Subject: [PATCH 133/202] Update hyperopt samples docstring --- freqtrade/templates/base_hyperopt.py.j2 | 15 +++++++++------ freqtrade/templates/sample_hyperopt.py | 19 ++++++++++++------- .../templates/sample_hyperopt_advanced.py | 7 ++++--- 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/freqtrade/templates/base_hyperopt.py.j2 b/freqtrade/templates/base_hyperopt.py.j2 index 05ba08b81..08178da4b 100644 --- a/freqtrade/templates/base_hyperopt.py.j2 +++ b/freqtrade/templates/base_hyperopt.py.j2 @@ -21,7 +21,7 @@ class {{ hyperopt }}(IHyperOpt): """ This is a Hyperopt template to get you started. - More information in https://github.com/freqtrade/freqtrade/blob/develop/docs/hyperopt.md + More information in the documentation: https://www.freqtrade.io/en/latest/hyperopt/ You should: - Add any lib you need to build your hyperopt. @@ -29,11 +29,14 @@ class {{ hyperopt }}(IHyperOpt): You must keep: - The prototypes for the methods: populate_indicators, indicator_space, buy_strategy_generator. - The roi_space, generate_roi_table, stoploss_space methods are no longer required to be - copied in every custom hyperopt. However, you may override them if you need the - 'roi' and the 'stoploss' spaces that differ from the defaults offered by Freqtrade. - Sample implementation of these methods can be found in - https://github.com/freqtrade/freqtrade/blob/develop/user_data/hyperopts/sample_hyperopt_advanced.py + The methods roi_space, generate_roi_table and stoploss_space are not required + and are provided by default. + However, you may override them if you need 'roi' and 'stoploss' spaces that + differ from the defaults offered by Freqtrade. + Sample implementation of these methods will be copied to `user_data/hyperopts` when + creating the user-data directory using `freqtrade create-userdir --userdir user_data`, + or is available online under the following URL: + https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/templates/sample_hyperopt_advanced.py. """ @staticmethod diff --git a/freqtrade/templates/sample_hyperopt.py b/freqtrade/templates/sample_hyperopt.py index f1dcb404a..0baa00442 100644 --- a/freqtrade/templates/sample_hyperopt.py +++ b/freqtrade/templates/sample_hyperopt.py @@ -20,23 +20,28 @@ import freqtrade.vendor.qtpylib.indicators as qtpylib class SampleHyperOpt(IHyperOpt): """ This is a sample Hyperopt to inspire you. - Feel free to customize it. - More information in https://github.com/freqtrade/freqtrade/blob/develop/docs/hyperopt.md + More information in the documentation: https://www.freqtrade.io/en/latest/hyperopt/ You should: - Rename the class name to some unique name. - Add any methods you want to build your hyperopt. - Add any lib you need to build your hyperopt. + An easier way to get a new hyperopt file is by using + `freqtrade new-hyperopt --hyperopt MyCoolHyperopt`. + You must keep: - The prototypes for the methods: populate_indicators, indicator_space, buy_strategy_generator. - The roi_space, generate_roi_table, stoploss_space methods are no longer required to be - copied in every custom hyperopt. However, you may override them if you need the - 'roi' and the 'stoploss' spaces that differ from the defaults offered by Freqtrade. - Sample implementation of these methods can be found in - https://github.com/freqtrade/freqtrade/blob/develop/user_data/hyperopts/sample_hyperopt_advanced.py + The methods roi_space, generate_roi_table and stoploss_space are not required + and are provided by default. + However, you may override them if you need 'roi' and 'stoploss' spaces that + differ from the defaults offered by Freqtrade. + Sample implementation of these methods will be copied to `user_data/hyperopts` when + creating the user-data directory using `freqtrade create-userdir --userdir user_data`, + or is available online under the following URL: + https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/templates/sample_hyperopt_advanced.py. """ @staticmethod diff --git a/freqtrade/templates/sample_hyperopt_advanced.py b/freqtrade/templates/sample_hyperopt_advanced.py index e66ef948b..c8067ad28 100644 --- a/freqtrade/templates/sample_hyperopt_advanced.py +++ b/freqtrade/templates/sample_hyperopt_advanced.py @@ -22,7 +22,7 @@ class AdvancedSampleHyperOpt(IHyperOpt): This is a sample hyperopt to inspire you. Feel free to customize it. - More information in https://github.com/freqtrade/freqtrade/blob/develop/docs/hyperopt.md + More information in the documentation: https://www.freqtrade.io/en/latest/hyperopt/ You should: - Rename the class name to some unique name. @@ -32,8 +32,9 @@ class AdvancedSampleHyperOpt(IHyperOpt): You must keep: - The prototypes for the methods: populate_indicators, indicator_space, buy_strategy_generator. - The roi_space, generate_roi_table, stoploss_space methods are no longer required to be - copied in every custom hyperopt. However, you may override them if you need the + The methods roi_space, generate_roi_table and stoploss_space are not required + and are provided by default. + However, you may override them if you need the 'roi' and the 'stoploss' spaces that differ from the defaults offered by Freqtrade. This sample illustrates how to override these methods. From 5da63d399be30908273ce7b2db7178e380a54dad Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 9 Mar 2020 17:38:25 +0100 Subject: [PATCH 134/202] Reduce default order_book_max to 1 --- config.json.example | 2 +- config_binance.json.example | 2 +- config_full.json.example | 2 +- config_kraken.json.example | 2 +- freqtrade/templates/base_config.json.j2 | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/config.json.example b/config.json.example index 46441e72d..8ebb092e1 100644 --- a/config.json.example +++ b/config.json.example @@ -23,7 +23,7 @@ "ask_strategy":{ "use_order_book": false, "order_book_min": 1, - "order_book_max": 9, + "order_book_max": 1, "use_sell_signal": true, "sell_profit_only": false, "ignore_roi_if_buy_signal": false diff --git a/config_binance.json.example b/config_binance.json.example index e2c9879b0..d324ce883 100644 --- a/config_binance.json.example +++ b/config_binance.json.example @@ -23,7 +23,7 @@ "ask_strategy":{ "use_order_book": false, "order_book_min": 1, - "order_book_max": 9, + "order_book_max": 1, "use_sell_signal": true, "sell_profit_only": false, "ignore_roi_if_buy_signal": false diff --git a/config_full.json.example b/config_full.json.example index f0414bd0d..181740b9a 100644 --- a/config_full.json.example +++ b/config_full.json.example @@ -38,7 +38,7 @@ "price_side": "ask", "use_order_book": false, "order_book_min": 1, - "order_book_max": 9, + "order_book_max": 1, "use_sell_signal": true, "sell_profit_only": false, "ignore_roi_if_buy_signal": false diff --git a/config_kraken.json.example b/config_kraken.json.example index 4f74d0b7d..dcf4c552a 100644 --- a/config_kraken.json.example +++ b/config_kraken.json.example @@ -23,7 +23,7 @@ "ask_strategy":{ "use_order_book": false, "order_book_min": 1, - "order_book_max": 9, + "order_book_max": 1, "use_sell_signal": true, "sell_profit_only": false, "ignore_roi_if_buy_signal": false diff --git a/freqtrade/templates/base_config.json.j2 b/freqtrade/templates/base_config.json.j2 index 0049d59a0..134719273 100644 --- a/freqtrade/templates/base_config.json.j2 +++ b/freqtrade/templates/base_config.json.j2 @@ -24,7 +24,7 @@ "price_side": "ask", "use_order_book": false, "order_book_min": 1, - "order_book_max": 9, + "order_book_max": 1, "use_sell_signal": true, "sell_profit_only": false, "ignore_roi_if_buy_signal": false From 74a17c7b7b45cac99238bcb684e7089c5b92c735 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 9 Mar 2020 17:38:35 +0100 Subject: [PATCH 135/202] Clarify warning in the documentation --- docs/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index 5580b9c68..aedd3e043 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -537,7 +537,7 @@ The idea here is to place the sell order early, to be ahead in the queue. A fixed slot (mirroring `bid_strategy.order_book_top`) can be defined by setting `ask_strategy.order_book_min` and `ask_strategy.order_book_max` to the same number. !!! Warning "Orderbook and stoploss_on_exchange" - Using `ask_strategy.order_book_max` higher than 1 may increase the risk, since an eventual [stoploss on exchange](#understand-order_types) will be needed to be cancelled as soon as the order is placed. + Using `ask_strategy.order_book_max` higher than 1 will increase the risk, since an eventual [stoploss on exchange](#understand-order_types) will be needed to be cancelled as soon as the order is placed. Also, the sell order will remain on the exchange for `unfilledtimeout.sell` (or until it's filled) - which can lead to missed stoplosses (even without stoploss on exchange). #### Sell price without Orderbook enabled From e0afbcd4af8e2b1e51a5eaca2b7e5ecd457f9f53 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 9 Mar 2020 17:41:44 +0100 Subject: [PATCH 136/202] Additional warning about order_book-max --- docs/configuration.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index aedd3e043..a9487a0ed 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -536,8 +536,14 @@ The idea here is to place the sell order early, to be ahead in the queue. A fixed slot (mirroring `bid_strategy.order_book_top`) can be defined by setting `ask_strategy.order_book_min` and `ask_strategy.order_book_max` to the same number. -!!! Warning "Orderbook and stoploss_on_exchange" - Using `ask_strategy.order_book_max` higher than 1 will increase the risk, since an eventual [stoploss on exchange](#understand-order_types) will be needed to be cancelled as soon as the order is placed. Also, the sell order will remain on the exchange for `unfilledtimeout.sell` (or until it's filled) - which can lead to missed stoplosses (even without stoploss on exchange). +!!! Warning "Order_book_max > 1 - increased Risk!" + Using `ask_strategy.order_book_max` higher than 1 will increase the risk, since an eventual [stoploss on exchange](#understand-order_types) will be needed to be cancelled as soon as the order is placed. + Also, the sell order will remain on the exchange for `unfilledtimeout.sell` (or until it's filled) - which can lead to missed stoplosses (with or without using stoploss_on_exchange). + +!!! Warning "Order_book_max > 1 in dry-run" + Using `ask_strategy.order_book_max` higher than 1 will result in improved dry-run results, since dry-run assumes orders to be filled almost instantly. + It is therefore advised to not use this setting for dry-runs. + #### Sell price without Orderbook enabled From 3eaae4661d3256ca27c92329b60661734c3ed716 Mon Sep 17 00:00:00 2001 From: orehunt Date: Mon, 9 Mar 2020 07:39:23 +0100 Subject: [PATCH 137/202] check again for emptiness after trimming dataframe --- freqtrade/data/history/idatahandler.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/freqtrade/data/history/idatahandler.py b/freqtrade/data/history/idatahandler.py index df03e7713..87810c95f 100644 --- a/freqtrade/data/history/idatahandler.py +++ b/freqtrade/data/history/idatahandler.py @@ -160,6 +160,13 @@ class IDataHandler(ABC): if timerange_startup: self._validate_pairdata(pair, pairdf, timerange_startup) pairdf = trim_dataframe(pairdf, timerange_startup) + if pairdf.empty: + if warn_no_data: + logger.warning( + f'No history data for pair: "{pair}", timeframe: {timeframe}. ' + 'Use `freqtrade download-data` to download the data' + ) + return pairdf # incomplete candles should only be dropped if we didn't trim the end beforehand. return clean_ohlcv_dataframe(pairdf, timeframe, From 2f5fc731bba51239a3195cbd0fb79e9e58a20d33 Mon Sep 17 00:00:00 2001 From: Fredrik81 Date: Mon, 9 Mar 2020 18:53:30 +0100 Subject: [PATCH 138/202] Removed overwrite option --- docs/utils.md | 13 +++++++------ freqtrade/commands/cli_options.py | 4 ++-- freqtrade/commands/hyperopt_commands.py | 8 +------- freqtrade/optimize/hyperopt.py | 6 +++--- tests/commands/test_commands.py | 2 +- 5 files changed, 14 insertions(+), 19 deletions(-) diff --git a/docs/utils.md b/docs/utils.md index dd7a9dfe3..eb71c509c 100644 --- a/docs/utils.md +++ b/docs/utils.md @@ -429,6 +429,7 @@ usage: freqtrade hyperopt-list [-h] [-v] [--logfile FILE] [-V] [-c PATH] [--min-total-profit FLOAT] [--max-total-profit FLOAT] [--no-color] [--print-json] [--no-details] + [--export-csv FILE] optional arguments: -h, --help show this help message and exit @@ -450,9 +451,8 @@ optional arguments: useful if you are redirecting output to a file. --print-json Print best result detailization in JSON format. --no-details Do not print best epoch details. - --export-csv FILE Export to CSV-File. Put + in front of filename to - overwrite. This will disable table print. Example: - --export-csv +hyperopt.csv + --export-csv FILE Export to CSV-File. This will disable table print. + Example: --export-csv hyperopt.csv Common arguments: -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). @@ -461,9 +461,10 @@ Common arguments: details. -V, --version show program's version number and exit -c PATH, --config PATH - Specify configuration file (default: `config.json`). - Multiple --config options may be used. Can be set to - `-` to read config from stdin. + Specify configuration file (default: + `userdir/config.json` or `config.json` whichever + exists). Multiple --config options may be used. Can be + set to `-` to read config from stdin. -d PATH, --datadir PATH Path to directory with historical backtesting data. --userdir PATH, --user-data-dir PATH diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py index b782c2fb9..8548bd887 100644 --- a/freqtrade/commands/cli_options.py +++ b/freqtrade/commands/cli_options.py @@ -223,9 +223,9 @@ AVAILABLE_CLI_OPTIONS = { ), "export_csv": Arg( '--export-csv', - help='Export to CSV-File. Put + in front of filename to overwrite.' + help='Export to CSV-File.' ' This will disable table print.' - ' Example: --export-csv +hyperopt.csv', + ' Example: --export-csv hyperopt.csv', metavar='FILE', ), "hyperopt_jobs": Arg( diff --git a/freqtrade/commands/hyperopt_commands.py b/freqtrade/commands/hyperopt_commands.py index efc9aba88..5b2388252 100755 --- a/freqtrade/commands/hyperopt_commands.py +++ b/freqtrade/commands/hyperopt_commands.py @@ -63,14 +63,8 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None: Hyperopt.print_epoch_details(results, total_epochs, print_json, no_header) if trials and export_csv: - overwrite_csv = False - if export_csv[0] == '+': - overwrite_csv = True - export_csv = export_csv[1:] - Hyperopt.export_csv_file( - config, trials, total_epochs, - not filteroptions['only_best'], export_csv, overwrite_csv + config, trials, total_epochs, not filteroptions['only_best'], export_csv ) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index d12e9cd45..8e0eba4d7 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -385,7 +385,7 @@ class Hyperopt: @staticmethod def export_csv_file(config: dict, results: list, total_epochs: int, highlight_best: bool, - csv_file: str, overwrite: bool) -> None: + csv_file: str) -> None: """ Log result to csv-file """ @@ -393,8 +393,8 @@ class Hyperopt: return # Verification for overwrite - if not overwrite and path.isfile(csv_file): - logging.error("CSV-File already exists and no overwrite specified!") + if path.isfile(csv_file): + logging.error("CSV-File already exists!") return try: diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index f404a4671..4530cd03d 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -905,7 +905,7 @@ def test_hyperopt_list(mocker, capsys, hyperopt_results): args = [ "hyperopt-list", "--no-details", - "--export-csv", "+test_file.csv" + "--export-csv", "test_file.csv" ] pargs = get_args(args) pargs['config'] = None From bd158eefd29a3cdec29887e7f54895935a379179 Mon Sep 17 00:00:00 2001 From: Fredrik81 Date: Tue, 10 Mar 2020 03:02:52 +0100 Subject: [PATCH 139/202] Fixed loggin --- freqtrade/optimize/hyperopt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 8e0eba4d7..1b6343208 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -394,13 +394,13 @@ class Hyperopt: # Verification for overwrite if path.isfile(csv_file): - logging.error("CSV-File already exists!") + logger.error("CSV-File already exists!") return try: io.open(csv_file, 'w+').close() except IOError: - logging.error("Filed to create/overwrite CSV-File!") + logger.error("Filed to create CSV-File!") return trials = json_normalize(results, max_level=1) From 42038da7f1f47d5e34cc10af04f9b42c5f6e4533 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 10 Mar 2020 07:57:25 +0100 Subject: [PATCH 140/202] Update docs/configuration.md Co-Authored-By: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> --- docs/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index a9487a0ed..1fca61fdb 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -541,7 +541,7 @@ A fixed slot (mirroring `bid_strategy.order_book_top`) can be defined by setting Also, the sell order will remain on the exchange for `unfilledtimeout.sell` (or until it's filled) - which can lead to missed stoplosses (with or without using stoploss_on_exchange). !!! Warning "Order_book_max > 1 in dry-run" - Using `ask_strategy.order_book_max` higher than 1 will result in improved dry-run results, since dry-run assumes orders to be filled almost instantly. + Using `ask_strategy.order_book_max` higher than 1 will result in improper dry-run results (significantly better than real orders executed on exchange), since dry-run assumes orders to be filled almost instantly. It is therefore advised to not use this setting for dry-runs. From f148b5f73450861d8f416f0e3b03d355c568ec6e Mon Sep 17 00:00:00 2001 From: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> Date: Tue, 10 Mar 2020 10:38:37 +0300 Subject: [PATCH 141/202] cosmetics in lambdas --- freqtrade/optimize/hyperopt.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 1b6343208..4c32a0543 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -332,10 +332,10 @@ class Hyperopt: lambda x: '{}/{}'.format(str(x).rjust(len(str(total_epochs)), ' '), total_epochs) ) trials['Avg profit'] = trials['Avg profit'].apply( - lambda x: ('{:,.2f}%'.format(x)).rjust(7, ' ') if not isna(x) else "--".rjust(7, ' ') + lambda x: '{:,.2f}%'.format(x).rjust(7, ' ') if not isna(x) else "--".rjust(7, ' ') ) trials['Avg duration'] = trials['Avg duration'].apply( - lambda x: ('{:,.1f} m'.format(x)).rjust(7, ' ') if not isna(x) else "--".rjust(7, ' ') + lambda x: '{:,.1f} m'.format(x).rjust(7, ' ') if not isna(x) else "--".rjust(7, ' ') ) trials['Objective'] = trials['Objective'].apply( lambda x: '{:,.5f}'.format(x).rjust(8, ' ') if x != 100000 else "N/A".rjust(8, ' ') @@ -426,10 +426,10 @@ class Hyperopt: lambda x: '{:,.2f}'.format(x) if not isna(x) else "" ) trials['Avg profit'] = trials['Avg profit'].apply( - lambda x: ('{:,.2f}%'.format(x)) if not isna(x) else "" + lambda x: '{:,.2f}%'.format(x) if not isna(x) else "" ) trials['Avg duration'] = trials['Avg duration'].apply( - lambda x: ('{:,.1f} m'.format(x)) if not isna(x) else "" + lambda x: '{:,.1f} m'.format(x) if not isna(x) else "" ) trials['Objective'] = trials['Objective'].apply( lambda x: '{:,.5f}'.format(x) if x != 100000 else "" From a046c4829c01ad2934337c59a304b181cb4f2a23 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 10 Mar 2020 09:03:44 +0100 Subject: [PATCH 142/202] Apply suggestions from code review Co-Authored-By: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> --- docs/configuration.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 1fca61fdb..76df5bd08 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -536,9 +536,9 @@ The idea here is to place the sell order early, to be ahead in the queue. A fixed slot (mirroring `bid_strategy.order_book_top`) can be defined by setting `ask_strategy.order_book_min` and `ask_strategy.order_book_max` to the same number. -!!! Warning "Order_book_max > 1 - increased Risk!" - Using `ask_strategy.order_book_max` higher than 1 will increase the risk, since an eventual [stoploss on exchange](#understand-order_types) will be needed to be cancelled as soon as the order is placed. - Also, the sell order will remain on the exchange for `unfilledtimeout.sell` (or until it's filled) - which can lead to missed stoplosses (with or without using stoploss_on_exchange). +!!! Warning "Order_book_max > 1 - increased risks for stoplosses!" + Using `ask_strategy.order_book_max` higher than 1 will increase the risk the stoploss on exchange is cancelled too early, since an eventual [stoploss on exchange](#understand-order_types) will be cancelled as soon as the order is placed. + Also, the sell order will remain on the exchange for `unfilledtimeout.sell` (or until it's filled) - which can lead to missed stoplosses (with or without using stoploss on exchange). !!! Warning "Order_book_max > 1 in dry-run" Using `ask_strategy.order_book_max` higher than 1 will result in improper dry-run results (significantly better than real orders executed on exchange), since dry-run assumes orders to be filled almost instantly. From f7ad6c20c7276ca6f76721d8195034ec2993bb87 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Tue, 10 Mar 2020 12:02:23 +0300 Subject: [PATCH 143/202] Do not allow unlimited stake_amount for hyperopt --- freqtrade/commands/optimize_commands.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/freqtrade/commands/optimize_commands.py b/freqtrade/commands/optimize_commands.py index a2d1b4601..2fc605926 100644 --- a/freqtrade/commands/optimize_commands.py +++ b/freqtrade/commands/optimize_commands.py @@ -17,10 +17,15 @@ def setup_optimize_configuration(args: Dict[str, Any], method: RunMode) -> Dict[ """ config = setup_utils_configuration(args, method) - if method == RunMode.BACKTEST: - if config['stake_amount'] == constants.UNLIMITED_STAKE_AMOUNT: - raise DependencyException('stake amount could not be "%s" for backtesting' % - constants.UNLIMITED_STAKE_AMOUNT) + no_unlimited_runmodes = { + RunMode.BACKTEST: 'backtesting', + RunMode.HYPEROPT: 'hyperoptimization', + } + if (method in no_unlimited_runmodes.keys() and + config['stake_amount'] == constants.UNLIMITED_STAKE_AMOUNT): + raise DependencyException( + f'The value of `stake_amount` cannot be set as "{constants.UNLIMITED_STAKE_AMOUNT}" ' + f'for {no_unlimited_runmodes[method]}') return config From 81b6a950ac58e231e7e29bd1429b34b40b40c1ff Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Tue, 10 Mar 2020 12:42:11 +0300 Subject: [PATCH 144/202] Adjust test for backtesting --- tests/optimize/test_backtesting.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index 96855dc9d..702337463 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -241,7 +241,7 @@ def test_setup_optimize_configuration_unlimited_stake_amount(mocker, default_con '--strategy', 'DefaultStrategy', ] - with pytest.raises(DependencyException, match=r'.*stake amount.*'): + with pytest.raises(DependencyException, match=r'.`stake_amount`.*'): setup_optimize_configuration(get_args(args), RunMode.BACKTEST) From 1b6e77649a437a75f4eae10d78d3fc0bc7dd7ba7 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Tue, 10 Mar 2020 12:42:31 +0300 Subject: [PATCH 145/202] Add test for hyperopt --- tests/optimize/test_hyperopt.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index 0406157f6..c0bddd085 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -10,10 +10,11 @@ import pytest from arrow import Arrow from filelock import Timeout +from freqtrade import constants from freqtrade.commands.optimize_commands import (setup_optimize_configuration, start_hyperopt) from freqtrade.data.history import load_data -from freqtrade.exceptions import OperationalException +from freqtrade.exceptions import DependencyException, OperationalException from freqtrade.optimize.default_hyperopt import DefaultHyperOpt from freqtrade.optimize.default_hyperopt_loss import DefaultHyperOptLoss from freqtrade.optimize.hyperopt import Hyperopt @@ -158,6 +159,21 @@ def test_setup_hyperopt_configuration_with_arguments(mocker, default_conf, caplo assert log_has('Parameter --print-all detected ...', caplog) +def test_setup_hyperopt_configuration_unlimited_stake_amount(mocker, default_conf, caplog) -> None: + default_conf['stake_amount'] = constants.UNLIMITED_STAKE_AMOUNT + + patched_configuration_load_config_file(mocker, default_conf) + + args = [ + 'hyperopt', + '--config', 'config.json', + '--hyperopt', 'DefaultHyperOpt', + ] + + with pytest.raises(DependencyException, match=r'.`stake_amount`.*'): + setup_optimize_configuration(get_args(args), RunMode.HYPEROPT) + + def test_hyperoptresolver(mocker, default_conf, caplog) -> None: patched_configuration_load_config_file(mocker, default_conf) From 73c19da4b988e13db70ec6f434baf773aa71faba Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Tue, 10 Mar 2020 13:44:16 +0300 Subject: [PATCH 146/202] Adjust handling of zero stdev in loss functions --- freqtrade/optimize/hyperopt_loss_sharpe.py | 2 +- freqtrade/optimize/hyperopt_loss_sharpe_daily.py | 2 +- freqtrade/optimize/hyperopt_loss_sortino.py | 2 +- freqtrade/optimize/hyperopt_loss_sortino_daily.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/freqtrade/optimize/hyperopt_loss_sharpe.py b/freqtrade/optimize/hyperopt_loss_sharpe.py index a4ec6f90a..29377bdd5 100644 --- a/freqtrade/optimize/hyperopt_loss_sharpe.py +++ b/freqtrade/optimize/hyperopt_loss_sharpe.py @@ -36,7 +36,7 @@ class SharpeHyperOptLoss(IHyperOptLoss): expected_returns_mean = total_profit.sum() / days_period up_stdev = np.std(total_profit) - if (np.std(total_profit) != 0.): + if up_stdev != 0: sharp_ratio = expected_returns_mean / up_stdev * np.sqrt(365) else: # Define high (negative) sharpe ratio to be clear that this is NOT optimal. diff --git a/freqtrade/optimize/hyperopt_loss_sharpe_daily.py b/freqtrade/optimize/hyperopt_loss_sharpe_daily.py index 5a8ebaa11..e4cd1d749 100644 --- a/freqtrade/optimize/hyperopt_loss_sharpe_daily.py +++ b/freqtrade/optimize/hyperopt_loss_sharpe_daily.py @@ -51,7 +51,7 @@ class SharpeHyperOptLossDaily(IHyperOptLoss): expected_returns_mean = total_profit.mean() up_stdev = total_profit.std() - if (up_stdev != 0.): + if up_stdev != 0: sharp_ratio = expected_returns_mean / up_stdev * math.sqrt(days_in_year) else: # Define high (negative) sharpe ratio to be clear that this is NOT optimal. diff --git a/freqtrade/optimize/hyperopt_loss_sortino.py b/freqtrade/optimize/hyperopt_loss_sortino.py index 83f644a43..d470a9977 100644 --- a/freqtrade/optimize/hyperopt_loss_sortino.py +++ b/freqtrade/optimize/hyperopt_loss_sortino.py @@ -39,7 +39,7 @@ class SortinoHyperOptLoss(IHyperOptLoss): results.loc[total_profit < 0, 'downside_returns'] = results['profit_percent'] down_stdev = np.std(results['downside_returns']) - if np.std(total_profit) != 0.0: + if down_stdev != 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. diff --git a/freqtrade/optimize/hyperopt_loss_sortino_daily.py b/freqtrade/optimize/hyperopt_loss_sortino_daily.py index 16dc26142..cd6a8bcc2 100644 --- a/freqtrade/optimize/hyperopt_loss_sortino_daily.py +++ b/freqtrade/optimize/hyperopt_loss_sortino_daily.py @@ -59,7 +59,7 @@ class SortinoHyperOptLossDaily(IHyperOptLoss): # where P = sum_daily["profit_percent_after_slippage"] down_stdev = math.sqrt((total_downside**2).sum() / len(total_downside)) - if (down_stdev != 0.): + 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. From 2b1c146940633b50dc0d9a9d225f44cc893bb088 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 10 Mar 2020 16:05:33 +0100 Subject: [PATCH 147/202] Add default volume > 0 filter --- docs/hyperopt.md | 3 +++ freqtrade/templates/base_hyperopt.py.j2 | 6 ++++++ freqtrade/templates/sample_hyperopt.py | 6 ++++++ freqtrade/templates/sample_hyperopt_advanced.py | 6 ++++++ 4 files changed, 21 insertions(+) diff --git a/docs/hyperopt.md b/docs/hyperopt.md index 9bc5888ce..1293c7ab4 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -159,6 +159,9 @@ So let's write the buy strategy using these values: dataframe['macd'], dataframe['macdsignal'] )) + # Check that volume is not 0 + conditions.append(dataframe['volume'] > 0) + if conditions: dataframe.loc[ reduce(lambda x, y: x & y, conditions), diff --git a/freqtrade/templates/base_hyperopt.py.j2 b/freqtrade/templates/base_hyperopt.py.j2 index 08178da4b..ec787cbb6 100644 --- a/freqtrade/templates/base_hyperopt.py.j2 +++ b/freqtrade/templates/base_hyperopt.py.j2 @@ -66,6 +66,9 @@ class {{ hyperopt }}(IHyperOpt): dataframe['close'], dataframe['sar'] )) + # Check that the candle had volume + conditions.append(dataframe['volume'] > 0) + if conditions: dataframe.loc[ reduce(lambda x, y: x & y, conditions), @@ -111,6 +114,9 @@ class {{ hyperopt }}(IHyperOpt): dataframe['sar'], dataframe['close'] )) + # Check that the candle had volume + conditions.append(dataframe['volume'] > 0) + if conditions: dataframe.loc[ reduce(lambda x, y: x & y, conditions), diff --git a/freqtrade/templates/sample_hyperopt.py b/freqtrade/templates/sample_hyperopt.py index 0baa00442..0b6d030db 100644 --- a/freqtrade/templates/sample_hyperopt.py +++ b/freqtrade/templates/sample_hyperopt.py @@ -78,6 +78,9 @@ class SampleHyperOpt(IHyperOpt): dataframe['close'], dataframe['sar'] )) + # Check that volume is not 0 + conditions.append(dataframe['volume'] > 0) + if conditions: dataframe.loc[ reduce(lambda x, y: x & y, conditions), @@ -138,6 +141,9 @@ class SampleHyperOpt(IHyperOpt): dataframe['sar'], dataframe['close'] )) + # Check that volume is not 0 + conditions.append(dataframe['volume'] > 0) + if conditions: dataframe.loc[ reduce(lambda x, y: x & y, conditions), diff --git a/freqtrade/templates/sample_hyperopt_advanced.py b/freqtrade/templates/sample_hyperopt_advanced.py index c8067ad28..7f05c4430 100644 --- a/freqtrade/templates/sample_hyperopt_advanced.py +++ b/freqtrade/templates/sample_hyperopt_advanced.py @@ -93,6 +93,9 @@ class AdvancedSampleHyperOpt(IHyperOpt): dataframe['close'], dataframe['sar'] )) + # Check that volume is not 0 + conditions.append(dataframe['volume'] > 0) + if conditions: dataframe.loc[ reduce(lambda x, y: x & y, conditions), @@ -153,6 +156,9 @@ class AdvancedSampleHyperOpt(IHyperOpt): dataframe['sar'], dataframe['close'] )) + # Check that volume is not 0 + conditions.append(dataframe['volume'] > 0) + if conditions: dataframe.loc[ reduce(lambda x, y: x & y, conditions), From 129a88d5da1879e1cae93e1771a995da6d5f0bfc Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 11 Mar 2020 19:53:28 +0100 Subject: [PATCH 148/202] Extract emptyness check to it's own method --- freqtrade/data/history/idatahandler.py | 27 ++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/freqtrade/data/history/idatahandler.py b/freqtrade/data/history/idatahandler.py index 87810c95f..bde9612f2 100644 --- a/freqtrade/data/history/idatahandler.py +++ b/freqtrade/data/history/idatahandler.py @@ -147,12 +147,7 @@ class IDataHandler(ABC): pairdf = self._ohlcv_load(pair, timeframe, timerange=timerange_startup) - if pairdf.empty: - if warn_no_data: - logger.warning( - f'No history data for pair: "{pair}", timeframe: {timeframe}. ' - 'Use `freqtrade download-data` to download the data' - ) + if self._check_empty_df(pairdf, pair, timeframe, warn_no_data): return pairdf else: enddate = pairdf.iloc[-1]['date'] @@ -160,12 +155,7 @@ class IDataHandler(ABC): if timerange_startup: self._validate_pairdata(pair, pairdf, timerange_startup) pairdf = trim_dataframe(pairdf, timerange_startup) - if pairdf.empty: - if warn_no_data: - logger.warning( - f'No history data for pair: "{pair}", timeframe: {timeframe}. ' - 'Use `freqtrade download-data` to download the data' - ) + if self._check_empty_df(pairdf, pair, timeframe, warn_no_data): return pairdf # incomplete candles should only be dropped if we didn't trim the end beforehand. @@ -175,6 +165,19 @@ class IDataHandler(ABC): drop_incomplete=(drop_incomplete and enddate == pairdf.iloc[-1]['date'])) + def _check_empty_df(self, pairdf: DataFrame, pair: str, timeframe: str, warn_no_data: bool): + """ + Warn on empty dataframe + """ + if pairdf.empty: + if warn_no_data: + logger.warning( + f'No history data for pair: "{pair}", timeframe: {timeframe}. ' + 'Use `freqtrade download-data` to download the data' + ) + return True + return False + def _validate_pairdata(self, pair, pairdata: DataFrame, timerange: TimeRange): """ Validates pairdata for missing data at start end end and logs warnings. From 6f67b8d9b900961afb93508214d981925261d3ec Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 12 Mar 2020 19:50:46 +0100 Subject: [PATCH 149/202] iCheck after clean_dataframe, too --- freqtrade/data/history/idatahandler.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/freqtrade/data/history/idatahandler.py b/freqtrade/data/history/idatahandler.py index bde9612f2..b0a6a97dc 100644 --- a/freqtrade/data/history/idatahandler.py +++ b/freqtrade/data/history/idatahandler.py @@ -159,11 +159,13 @@ class IDataHandler(ABC): return pairdf # incomplete candles should only be dropped if we didn't trim the end beforehand. - return clean_ohlcv_dataframe(pairdf, timeframe, - pair=pair, - fill_missing=fill_missing, - drop_incomplete=(drop_incomplete and - enddate == pairdf.iloc[-1]['date'])) + pairdf = clean_ohlcv_dataframe(pairdf, timeframe, + pair=pair, + fill_missing=fill_missing, + drop_incomplete=(drop_incomplete and + enddate == pairdf.iloc[-1]['date'])) + self._check_empty_df(pairdf, pair, timeframe, warn_no_data) + return pairdf def _check_empty_df(self, pairdf: DataFrame, pair: str, timeframe: str, warn_no_data: bool): """ From ebb0187f4040aa7f23ed54cacd8faa9919640994 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Fri, 13 Mar 2020 03:54:56 +0300 Subject: [PATCH 150/202] dataframe -> df_analyzed in backtesting and edge --- freqtrade/edge/edge_positioning.py | 14 +++++++------- freqtrade/optimize/backtesting.py | 10 +++++----- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/freqtrade/edge/edge_positioning.py b/freqtrade/edge/edge_positioning.py index a24e29efb..256b67383 100644 --- a/freqtrade/edge/edge_positioning.py +++ b/freqtrade/edge/edge_positioning.py @@ -137,10 +137,10 @@ class Edge: pair_data = pair_data.sort_values(by=['date']) pair_data = pair_data.reset_index(drop=True) - dataframe = self.strategy.advise_sell( + df_analyzed = self.strategy.advise_sell( self.strategy.advise_buy(pair_data, {'pair': pair}), {'pair': pair})[headers].copy() - trades += self._find_trades_for_stoploss_range(dataframe, pair, self._stoploss_range) + trades += self._find_trades_for_stoploss_range(df_analyzed, pair, self._stoploss_range) # If no trade found then exit if len(trades) == 0: @@ -359,11 +359,11 @@ class Edge: # Returning a list of pairs in order of "expectancy" return final - def _find_trades_for_stoploss_range(self, dataframe, pair, stoploss_range): - buy_column = dataframe['buy'].values - sell_column = dataframe['sell'].values - date_column = dataframe['date'].values - ohlc_columns = dataframe[['open', 'high', 'low', 'close']].values + def _find_trades_for_stoploss_range(self, df, pair, stoploss_range): + buy_column = df['buy'].values + sell_column = df['sell'].values + date_column = df['date'].values + ohlc_columns = df[['open', 'high', 'low', 'close']].values result: list = [] for stoploss in stoploss_range: diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 949c072c5..210fe3c66 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -164,19 +164,19 @@ class Backtesting: pair_data.loc[:, 'buy'] = 0 # cleanup from previous run pair_data.loc[:, 'sell'] = 0 # cleanup from previous run - dataframe = self.strategy.advise_sell( + df_analyzed = self.strategy.advise_sell( self.strategy.advise_buy(pair_data, {'pair': pair}), {'pair': pair})[headers].copy() # To avoid using data from future, we use buy/sell signals shifted # from the previous candle - dataframe.loc[:, 'buy'] = dataframe['buy'].shift(1) - dataframe.loc[:, 'sell'] = dataframe['sell'].shift(1) + df_analyzed.loc[:, 'buy'] = df_analyzed['buy'].shift(1) + df_analyzed.loc[:, 'sell'] = df_analyzed['sell'].shift(1) - dataframe.drop(dataframe.head(1).index, inplace=True) + df_analyzed.drop(df_analyzed.head(1).index, inplace=True) # Convert from Pandas to list for performance reasons # (Looping Pandas is slow.) - data[pair] = [x for x in dataframe.itertuples()] + data[pair] = [x for x in df_analyzed.itertuples()] return data def _get_close_rate(self, sell_row, trade: Trade, sell: SellCheckTuple, From b2952cd42ad22ecb0e623909524c07ac273803f3 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Fri, 13 Mar 2020 03:58:16 +0300 Subject: [PATCH 151/202] remove redundant dict --- freqtrade/plot/plotting.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/freqtrade/plot/plotting.py b/freqtrade/plot/plotting.py index cfbda6714..220056d2d 100644 --- a/freqtrade/plot/plotting.py +++ b/freqtrade/plot/plotting.py @@ -454,10 +454,8 @@ def load_and_plot_trades(config: Dict[str, Any]): for pair, data in plot_elements["ohlcv"].items(): pair_counter += 1 logger.info("analyse pair %s", pair) - ohlcv = {} - ohlcv[pair] = data - dataframe = strategy.analyze_ticker(ohlcv[pair], {'pair': pair}) + dataframe = strategy.analyze_ticker(data, {'pair': pair}) trades_pair = trades.loc[trades['pair'] == pair] trades_pair = extract_trades_of_period(dataframe, trades_pair) From ddfe5b5f1cb4788936699afbc36c1f19e85d2fc4 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Fri, 13 Mar 2020 04:00:24 +0300 Subject: [PATCH 152/202] dataframe -> df_analyzed in plotting --- freqtrade/plot/plotting.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/freqtrade/plot/plotting.py b/freqtrade/plot/plotting.py index 220056d2d..be7be2de0 100644 --- a/freqtrade/plot/plotting.py +++ b/freqtrade/plot/plotting.py @@ -455,13 +455,13 @@ def load_and_plot_trades(config: Dict[str, Any]): pair_counter += 1 logger.info("analyse pair %s", pair) - dataframe = strategy.analyze_ticker(data, {'pair': pair}) + df_analyzed = strategy.analyze_ticker(data, {'pair': pair}) trades_pair = trades.loc[trades['pair'] == pair] - trades_pair = extract_trades_of_period(dataframe, trades_pair) + trades_pair = extract_trades_of_period(df_analyzed, trades_pair) fig = generate_candlestick_graph( pair=pair, - data=dataframe, + data=df_analyzed, trades=trades_pair, indicators1=config.get("indicators1", []), indicators2=config.get("indicators2", []), From a7ed51c6424202dccb27f44a847721dbbf4eecde Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Fri, 13 Mar 2020 04:04:23 +0300 Subject: [PATCH 153/202] return back the name of the hyperopt data file --- freqtrade/optimize/hyperopt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index ed58db977..a6b13f93f 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -76,7 +76,7 @@ class Hyperopt: self.trials_file = (self.config['user_data_dir'] / 'hyperopt_results' / 'hyperopt_results.pickle') self.data_pickle_file = (self.config['user_data_dir'] / - 'hyperopt_results' / 'hyperopt_data.pkl') + 'hyperopt_results' / 'hyperopt_tickerdata.pkl') self.total_epochs = config.get('epochs', 0) self.current_best_loss = 100 From 59fadabb5ba01bc113c0d87cced31a7e7074df66 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Fri, 13 Mar 2020 20:26:14 +0300 Subject: [PATCH 154/202] Fix merging --- freqtrade/data/history/idatahandler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/data/history/idatahandler.py b/freqtrade/data/history/idatahandler.py index ae050103b..1bb4d5971 100644 --- a/freqtrade/data/history/idatahandler.py +++ b/freqtrade/data/history/idatahandler.py @@ -150,7 +150,7 @@ class IDataHandler(ABC): if self._check_empty_df(pairdf, pair, timeframe, warn_no_data): return pairdf else: - enddate = df.iloc[-1]['date'] + enddate = pairdf.iloc[-1]['date'] if timerange_startup: self._validate_pairdata(pair, pairdf, timerange_startup) From c56cbc21b1658102d912435fa00316ae3da51443 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 14 Mar 2020 10:42:01 +0100 Subject: [PATCH 155/202] Remove indexing warning in edge --- freqtrade/edge/edge_positioning.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/edge/edge_positioning.py b/freqtrade/edge/edge_positioning.py index 256b67383..d196ab4b3 100644 --- a/freqtrade/edge/edge_positioning.py +++ b/freqtrade/edge/edge_positioning.py @@ -317,7 +317,7 @@ class Edge: } # Group by (pair and stoploss) by applying above aggregator - df = results.groupby(['pair', 'stoploss'])['profit_abs', 'trade_duration'].agg( + df = results.groupby(['pair', 'stoploss'])[['profit_abs', 'trade_duration']].agg( groupby_aggregator).reset_index(col_level=1) # Dropping level 0 as we don't need it From 308d8fe2a9daf78fd58642c884dd6a9c4bd9a119 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 14 Mar 2020 10:44:46 +0100 Subject: [PATCH 156/202] Remove deprecation warnings due to date conversion --- tests/edge/test_edge.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/edge/test_edge.py b/tests/edge/test_edge.py index c68ac477c..3bebeee65 100644 --- a/tests/edge/test_edge.py +++ b/tests/edge/test_edge.py @@ -163,8 +163,8 @@ def test_edge_results(edge_conf, mocker, caplog, data) -> None: for c, trade in enumerate(data.trades): res = results.iloc[c] assert res.exit_type == trade.sell_reason - assert res.open_time == np.datetime64(_get_frame_time_from_offset(trade.open_tick)) - assert res.close_time == np.datetime64(_get_frame_time_from_offset(trade.close_tick)) + assert res.open_time == _get_frame_time_from_offset(trade.open_tick).replace(tzinfo=None) + assert res.close_time == _get_frame_time_from_offset(trade.close_tick).replace(tzinfo=None) def test_adjust(mocker, edge_conf): From 27faf12fdeef84f404aa7bf32f27e7591cc1a910 Mon Sep 17 00:00:00 2001 From: Fredrik81 Date: Sat, 14 Mar 2020 22:15:03 +0100 Subject: [PATCH 157/202] Fix if no file exists --- freqtrade/commands/arguments.py | 2 +- freqtrade/commands/cli_options.py | 5 +++++ freqtrade/data/btanalysis.py | 8 ++++++-- freqtrade/plot/plotting.py | 19 +++++++++++++++---- tests/data/test_btanalysis.py | 5 ++++- 5 files changed, 31 insertions(+), 8 deletions(-) diff --git a/freqtrade/commands/arguments.py b/freqtrade/commands/arguments.py index 1a8cca72b..66fa0b038 100644 --- a/freqtrade/commands/arguments.py +++ b/freqtrade/commands/arguments.py @@ -59,7 +59,7 @@ ARGS_DOWNLOAD_DATA = ["pairs", "pairs_file", "days", "download_trades", "exchang ARGS_PLOT_DATAFRAME = ["pairs", "indicators1", "indicators2", "plot_limit", "db_url", "trade_source", "export", "exportfilename", - "timerange", "ticker_interval"] + "timerange", "ticker_interval", "skip_trades"] ARGS_PLOT_PROFIT = ["pairs", "timerange", "export", "exportfilename", "db_url", "trade_source", "ticker_interval"] diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py index 42c697d56..187e0a424 100644 --- a/freqtrade/commands/cli_options.py +++ b/freqtrade/commands/cli_options.py @@ -413,6 +413,11 @@ AVAILABLE_CLI_OPTIONS = { metavar='INT', default=750, ), + "skip_trades": Arg( + '--skip-trades', + help='Skip using trades file from backtesting and DB.', + action='store_true', + ), "trade_source": Arg( '--trade-source', help='Specify the source for trades (Can be DB or file (backtest file)) ' diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index 681bf6734..fc938ad7e 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -111,7 +111,7 @@ def load_trades_from_db(db_url: str) -> pd.DataFrame: t.calc_profit(), t.calc_profit_ratio(), t.open_rate, t.close_rate, t.amount, (round((t.close_date.timestamp() - t.open_date.timestamp()) / 60, 2) - if t.close_date else None), + if t.close_date else None), t.sell_reason, t.fee_open, t.fee_close, t.open_rate_requested, @@ -129,12 +129,16 @@ def load_trades_from_db(db_url: str) -> pd.DataFrame: return trades -def load_trades(source: str, db_url: str, exportfilename: str) -> pd.DataFrame: +def load_trades(source: str, db_url: str, exportfilename: str, skip_trades: bool) -> pd.DataFrame: """ Based on configuration option "trade_source": * loads data from DB (using `db_url`) * loads data from backtestfile (using `exportfilename`) """ + if skip_trades: + df = pd.DataFrame(columns=BT_DATA_COLUMNS) + return df + if source == "DB": return load_trades_from_db(db_url) elif source == "file": diff --git a/freqtrade/plot/plotting.py b/freqtrade/plot/plotting.py index be7be2de0..cb2686878 100644 --- a/freqtrade/plot/plotting.py +++ b/freqtrade/plot/plotting.py @@ -3,6 +3,7 @@ from pathlib import Path from typing import Any, Dict, List import pandas as pd +from os.path import isfile from freqtrade.configuration import TimeRange from freqtrade.data.btanalysis import (calculate_max_drawdown, @@ -48,11 +49,21 @@ def init_plotscript(config): data_format=config.get('dataformat_ohlcv', 'json'), ) - trades = load_trades(config['trade_source'], - db_url=config.get('db_url'), - exportfilename=config.get('exportfilename'), - ) + skip_trades = False + if not isfile(config.get('exportfilename')) and config['trade_source'] == 'file': + logger.info("Backtest file is missing skipping trades.") + skip_trades = True + elif config.get('skip_trades', False): + skip_trades = True + + trades = load_trades( + config['trade_source'], + db_url=config.get('db_url'), + exportfilename=config.get('exportfilename'), + skip_trades=skip_trades + ) trades = trim_dataframe(trades, timerange, 'open_time') + return {"ohlcv": data, "trades": trades, "pairs": pairs, diff --git a/tests/data/test_btanalysis.py b/tests/data/test_btanalysis.py index da5d225b9..44e9c1072 100644 --- a/tests/data/test_btanalysis.py +++ b/tests/data/test_btanalysis.py @@ -104,6 +104,7 @@ def test_load_trades(default_conf, mocker): load_trades("DB", db_url=default_conf.get('db_url'), exportfilename=default_conf.get('exportfilename'), + skip_trades=False ) assert db_mock.call_count == 1 @@ -114,7 +115,9 @@ def test_load_trades(default_conf, mocker): default_conf['exportfilename'] = "testfile.json" load_trades("file", db_url=default_conf.get('db_url'), - exportfilename=default_conf.get('exportfilename'),) + exportfilename=default_conf.get('exportfilename'), + skip_trades=False + ) assert db_mock.call_count == 0 assert bt_mock.call_count == 1 From cf7e80f45d5d664c1dcb1cc92335cffb67186d12 Mon Sep 17 00:00:00 2001 From: Fredrik81 Date: Sat, 14 Mar 2020 23:55:13 +0100 Subject: [PATCH 158/202] Docs and logging --- docs/plotting.md | 62 +++++++++++++++++++++++++------------- freqtrade/plot/plotting.py | 2 +- 2 files changed, 42 insertions(+), 22 deletions(-) diff --git a/docs/plotting.md b/docs/plotting.md index 3eef8f8e7..ef28f0fe3 100644 --- a/docs/plotting.md +++ b/docs/plotting.md @@ -23,44 +23,64 @@ The `freqtrade plot-dataframe` subcommand shows an interactive graph with three Possible arguments: ``` -usage: freqtrade plot-dataframe [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] [--userdir PATH] [-s NAME] - [--strategy-path PATH] [-p PAIRS [PAIRS ...]] [--indicators1 INDICATORS1 [INDICATORS1 ...]] - [--indicators2 INDICATORS2 [INDICATORS2 ...]] [--plot-limit INT] [--db-url PATH] - [--trade-source {DB,file}] [--export EXPORT] [--export-filename PATH] [--timerange TIMERANGE] - [-i TICKER_INTERVAL] +usage: freqtrade plot-dataframe [-h] [-v] [--logfile FILE] [-V] [-c PATH] + [-d PATH] [--userdir PATH] [-s NAME] + [--strategy-path PATH] [-p PAIRS [PAIRS ...]] + [--indicators1 INDICATORS1 [INDICATORS1 ...]] + [--indicators2 INDICATORS2 [INDICATORS2 ...]] + [--plot-limit INT] [--db-url PATH] + [--trade-source {DB,file}] [--export EXPORT] + [--export-filename PATH] + [--timerange TIMERANGE] [-i TICKER_INTERVAL] + [--skip-trades] optional arguments: -h, --help show this help message and exit -p PAIRS [PAIRS ...], --pairs PAIRS [PAIRS ...] - Show profits for only these pairs. Pairs are space-separated. + Show profits for only these pairs. Pairs are space- + separated. --indicators1 INDICATORS1 [INDICATORS1 ...] - Set indicators from your strategy you want in the first row of the graph. Space-separated list. Example: + Set indicators from your strategy you want in the + first row of the graph. Space-separated list. Example: `ema3 ema5`. Default: `['sma', 'ema3', 'ema5']`. --indicators2 INDICATORS2 [INDICATORS2 ...] - Set indicators from your strategy you want in the third row of the graph. Space-separated list. Example: + Set indicators from your strategy you want in the + third row of the graph. Space-separated list. Example: `fastd fastk`. Default: `['macd', 'macdsignal']`. - --plot-limit INT Specify tick limit for plotting. Notice: too high values cause huge files. Default: 750. - --db-url PATH Override trades database URL, this is useful in custom deployments (default: `sqlite:///tradesv3.sqlite` - for Live Run mode, `sqlite:///tradesv3.dryrun.sqlite` for Dry Run). + --plot-limit INT Specify tick limit for plotting. Notice: too high + values cause huge files. Default: 750. + --db-url PATH Override trades database URL, this is useful in custom + deployments (default: `sqlite:///tradesv3.sqlite` for + Live Run mode, `sqlite:///tradesv3.dryrun.sqlite` for + Dry Run). --trade-source {DB,file} - Specify the source for trades (Can be DB or file (backtest file)) Default: file - --export EXPORT Export backtest results, argument are: trades. Example: `--export=trades` + Specify the source for trades (Can be DB or file + (backtest file)) Default: file + --export EXPORT Export backtest results, argument are: trades. + Example: `--export=trades` --export-filename PATH - Save backtest results to the file with this filename. Requires `--export` to be set as well. Example: - `--export-filename=user_data/backtest_results/backtest_today.json` + Save backtest results to the file with this filename. + Requires `--export` to be set as well. Example: + `--export-filename=user_data/backtest_results/backtest + _today.json` --timerange TIMERANGE Specify what timerange of data to use. -i TICKER_INTERVAL, --ticker-interval TICKER_INTERVAL - Specify ticker interval (`1m`, `5m`, `30m`, `1h`, `1d`). + Specify ticker interval (`1m`, `5m`, `30m`, `1h`, + `1d`). + --skip-trades Skip using trades file from backtesting and DB. Common arguments: -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). - --logfile FILE Log to the file specified. Special values are: 'syslog', 'journald'. See the documentation for more + --logfile FILE Log to the file specified. Special values are: + 'syslog', 'journald'. See the documentation for more details. -V, --version show program's version number and exit -c PATH, --config PATH - Specify configuration file (default: `config.json`). Multiple --config options may be used. Can be set to - `-` to read config from stdin. + Specify configuration file (default: + `userdir/config.json` or `config.json` whichever + exists). Multiple --config options may be used. Can be + set to `-` to read config from stdin. -d PATH, --datadir PATH Path to directory with historical backtesting data. --userdir PATH, --user-data-dir PATH @@ -68,9 +88,9 @@ Common arguments: Strategy arguments: -s NAME, --strategy NAME - Specify strategy class name which will be used by the bot. + Specify strategy class name which will be used by the + bot. --strategy-path PATH Specify additional strategy lookup path. - ``` Example: diff --git a/freqtrade/plot/plotting.py b/freqtrade/plot/plotting.py index cb2686878..8da61597f 100644 --- a/freqtrade/plot/plotting.py +++ b/freqtrade/plot/plotting.py @@ -51,7 +51,7 @@ def init_plotscript(config): skip_trades = False if not isfile(config.get('exportfilename')) and config['trade_source'] == 'file': - logger.info("Backtest file is missing skipping trades.") + logger.warning("Backtest file is missing skipping trades.") skip_trades = True elif config.get('skip_trades', False): skip_trades = True From 2c0980aa3ab2af40e46411819f2e53b76eae6fa4 Mon Sep 17 00:00:00 2001 From: Fredrik81 Date: Sun, 15 Mar 2020 00:09:08 +0100 Subject: [PATCH 159/202] Tests --- tests/data/test_btanalysis.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/data/test_btanalysis.py b/tests/data/test_btanalysis.py index 44e9c1072..95f371d7f 100644 --- a/tests/data/test_btanalysis.py +++ b/tests/data/test_btanalysis.py @@ -122,6 +122,18 @@ def test_load_trades(default_conf, mocker): assert db_mock.call_count == 0 assert bt_mock.call_count == 1 + db_mock.reset_mock() + bt_mock.reset_mock() + default_conf['exportfilename'] = "testfile.json" + load_trades("file", + db_url=default_conf.get('db_url'), + exportfilename=default_conf.get('exportfilename'), + skip_trades=True + ) + + assert db_mock.call_count == 0 + assert bt_mock.call_count == 0 + def test_combine_dataframes_with_mean(testdatadir): pairs = ["ETH/BTC", "ADA/BTC"] From 0f1640bed4691cc0d27ed1d665e77c8d2dd85944 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 15 Mar 2020 09:39:45 +0100 Subject: [PATCH 160/202] convert exportfilename to Path when config parsing --- freqtrade/configuration/configuration.py | 1 + freqtrade/data/btanalysis.py | 8 ++++++-- freqtrade/optimize/backtesting.py | 2 +- tests/data/test_btanalysis.py | 5 +++-- tests/optimize/test_backtesting.py | 1 + tests/test_plotting.py | 6 +++--- 6 files changed, 15 insertions(+), 8 deletions(-) diff --git a/freqtrade/configuration/configuration.py b/freqtrade/configuration/configuration.py index 0645d72be..ce2101441 100644 --- a/freqtrade/configuration/configuration.py +++ b/freqtrade/configuration/configuration.py @@ -196,6 +196,7 @@ class Configuration: if self.args.get('exportfilename'): self._args_to_config(config, argname='exportfilename', logstring='Storing backtest results to {} ...') + config['exportfilename'] = Path(config['exportfilename']) else: config['exportfilename'] = (config['user_data_dir'] / 'backtest_results/backtest-result.json') diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index 681bf6734..e8ec03fea 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -129,16 +129,20 @@ def load_trades_from_db(db_url: str) -> pd.DataFrame: return trades -def load_trades(source: str, db_url: str, exportfilename: str) -> pd.DataFrame: +def load_trades(source: str, db_url: str, exportfilename: Path) -> pd.DataFrame: """ Based on configuration option "trade_source": * loads data from DB (using `db_url`) * loads data from backtestfile (using `exportfilename`) + :param source: "DB" or "file" - specify source to load from + :param db_url: sqlalchemy formatted url to a database + :param exportfilename: Json file generated by backtesting + :return: DataFrame containing trades """ if source == "DB": return load_trades_from_db(db_url) elif source == "file": - return load_backtest_data(Path(exportfilename)) + return load_backtest_data(exportfilename) def extract_trades_of_period(dataframe: pd.DataFrame, trades: pd.DataFrame) -> pd.DataFrame: diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 210fe3c66..40e6590f7 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -421,7 +421,7 @@ class Backtesting: for strategy, results in all_results.items(): if self.config.get('export', False): - self._store_backtest_result(Path(self.config['exportfilename']), results, + self._store_backtest_result(self.config['exportfilename'], results, strategy if len(self.strategylist) > 1 else None) print(f"Result for strategy {strategy}") diff --git a/tests/data/test_btanalysis.py b/tests/data/test_btanalysis.py index da5d225b9..7513991ea 100644 --- a/tests/data/test_btanalysis.py +++ b/tests/data/test_btanalysis.py @@ -1,8 +1,9 @@ +from pathlib import Path from unittest.mock import MagicMock import pytest from arrow import Arrow -from pandas import DataFrame, DateOffset, to_datetime, Timestamp +from pandas import DataFrame, DateOffset, Timestamp, to_datetime from freqtrade.configuration import TimeRange from freqtrade.data.btanalysis import (BT_DATA_COLUMNS, @@ -111,7 +112,7 @@ def test_load_trades(default_conf, mocker): db_mock.reset_mock() bt_mock.reset_mock() - default_conf['exportfilename'] = "testfile.json" + default_conf['exportfilename'] = Path("testfile.json") load_trades("file", db_url=default_conf.get('db_url'), exportfilename=default_conf.get('exportfilename'),) diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index f78b44704..da23a9af4 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -224,6 +224,7 @@ def test_setup_bt_configuration_with_arguments(mocker, default_conf, caplog) -> assert 'export' in config assert log_has('Parameter --export detected: {} ...'.format(config['export']), caplog) assert 'exportfilename' in config + assert isinstance(config['exportfilename'], Path) assert log_has('Storing backtest results to {} ...'.format(config['exportfilename']), caplog) assert 'fee' in config diff --git a/tests/test_plotting.py b/tests/test_plotting.py index 42010ad0d..a5c965429 100644 --- a/tests/test_plotting.py +++ b/tests/test_plotting.py @@ -49,7 +49,7 @@ def test_init_plotscript(default_conf, mocker, testdatadir): default_conf['trade_source'] = "file" default_conf['ticker_interval'] = "5m" default_conf["datadir"] = testdatadir - default_conf['exportfilename'] = str(testdatadir / "backtest-result_test.json") + default_conf['exportfilename'] = testdatadir / "backtest-result_test.json" ret = init_plotscript(default_conf) assert "ohlcv" in ret assert "trades" in ret @@ -318,7 +318,7 @@ def test_start_plot_dataframe(mocker): def test_load_and_plot_trades(default_conf, mocker, caplog, testdatadir): default_conf['trade_source'] = 'file' default_conf["datadir"] = testdatadir - default_conf['exportfilename'] = str(testdatadir / "backtest-result_test.json") + default_conf['exportfilename'] = testdatadir / "backtest-result_test.json" default_conf['indicators1'] = ["sma5", "ema10"] default_conf['indicators2'] = ["macd"] default_conf['pairs'] = ["ETH/BTC", "LTC/BTC"] @@ -374,7 +374,7 @@ def test_start_plot_profit_error(mocker): def test_plot_profit(default_conf, mocker, testdatadir, caplog): default_conf['trade_source'] = 'file' default_conf["datadir"] = testdatadir - default_conf['exportfilename'] = str(testdatadir / "backtest-result_test.json") + default_conf['exportfilename'] = testdatadir / "backtest-result_test.json" default_conf['pairs'] = ["ETH/BTC", "LTC/BTC"] profit_mock = MagicMock() From 328dbd3930981103d2646e27e53156f104e59745 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 15 Mar 2020 15:04:48 +0100 Subject: [PATCH 161/202] Remove unnecessary parameter to generate_text_table_sell_reason --- freqtrade/optimize/backtesting.py | 7 +++---- freqtrade/optimize/optimize_reports.py | 8 ++++---- tests/optimize/test_optimize_reports.py | 6 ++---- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 40e6590f7..5ee5a058f 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -7,7 +7,7 @@ import logging from copy import deepcopy from datetime import datetime, timedelta from pathlib import Path -from typing import Any, Dict, List, NamedTuple, Optional +from typing import Any, Dict, List, NamedTuple, Optional, Tuple import arrow from pandas import DataFrame @@ -108,7 +108,7 @@ class Backtesting: # And the regular "stoploss" function would not apply to that case self.strategy.order_types['stoploss_on_exchange'] = False - def load_bt_data(self): + def load_bt_data(self) -> Tuple[Dict[str, DataFrame], TimeRange]: timerange = TimeRange.parse_timerange(None if self.config.get( 'timerange') is None else str(self.config.get('timerange'))) @@ -432,8 +432,7 @@ class Backtesting: print(' BACKTESTING REPORT '.center(len(table.splitlines()[0]), '=')) print(table) - table = generate_text_table_sell_reason(data, - stake_currency=self.config['stake_currency'], + table = generate_text_table_sell_reason(stake_currency=self.config['stake_currency'], max_open_trades=self.config['max_open_trades'], results=results) if isinstance(table, str): diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index 39bde50a8..ee29aa283 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -69,12 +69,12 @@ def generate_text_table(data: Dict[str, Dict], stake_currency: str, max_open_tra floatfmt=floatfmt, tablefmt="orgtbl", stralign="right") # type: ignore -def generate_text_table_sell_reason( - data: Dict[str, Dict], stake_currency: str, max_open_trades: int, results: DataFrame -) -> str: +def generate_text_table_sell_reason(stake_currency: str, max_open_trades: int, + results: DataFrame) -> str: """ Generate small table outlining Backtest results - :param data: Dict of containing data that was used during backtesting. + :param stake_currency: Stakecurrency used + :param max_open_trades: Max_open_trades parameter :param results: Dataframe containing the backtest results :return: pretty printed table with tabulate as string """ diff --git a/tests/optimize/test_optimize_reports.py b/tests/optimize/test_optimize_reports.py index 285ecaa02..5e68a5ef8 100644 --- a/tests/optimize/test_optimize_reports.py +++ b/tests/optimize/test_optimize_reports.py @@ -61,10 +61,8 @@ def test_generate_text_table_sell_reason(default_conf, mocker): '| stop_loss | 1 | 0 | 0 | 1 |' ' -10 | -10 | -0.2 | -5 |' ) - assert generate_text_table_sell_reason( - data={'ETH/BTC': {}}, - stake_currency='BTC', max_open_trades=2, - results=results) == result_str + assert generate_text_table_sell_reason(stake_currency='BTC', max_open_trades=2, + results=results) == result_str def test_generate_text_table_strategy(default_conf, mocker): From 6106d59e1a0122863849e03e671db698f02c5c96 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 15 Mar 2020 15:17:35 +0100 Subject: [PATCH 162/202] Move store_backtest_results to optimize_reports --- freqtrade/optimize/backtesting.py | 18 ------------------ freqtrade/optimize/optimize_reports.py | 26 +++++++++++++++++++++++++- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 5ee5a058f..9dd1b8429 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -6,7 +6,6 @@ This module contains the backtesting logic import logging from copy import deepcopy from datetime import datetime, timedelta -from pathlib import Path from typing import Any, Dict, List, NamedTuple, Optional, Tuple import arrow @@ -134,23 +133,6 @@ class Backtesting: return data, timerange - def _store_backtest_result(self, recordfilename: Path, results: DataFrame, - strategyname: Optional[str] = None) -> None: - - records = [(t.pair, t.profit_percent, t.open_time.timestamp(), - t.close_time.timestamp(), t.open_index - 1, t.trade_duration, - t.open_rate, t.close_rate, t.open_at_end, t.sell_reason.value) - for index, t in results.iterrows()] - - if records: - if strategyname: - # Inject strategyname to filename - recordfilename = Path.joinpath( - recordfilename.parent, - f'{recordfilename.stem}-{strategyname}').with_suffix(recordfilename.suffix) - logger.info(f'Dumping backtest results to {recordfilename}') - file_dump_json(recordfilename, records) - def _get_ohlcv_as_lists(self, processed: Dict) -> Dict[str, DataFrame]: """ Helper function to convert a processed dataframes into lists for performance reasons. diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index ee29aa283..fb407f681 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -1,9 +1,33 @@ +import logging from datetime import timedelta -from typing import Dict +from pathlib import Path +from typing import Dict, Optional from pandas import DataFrame from tabulate import tabulate +from freqtrade.misc import file_dump_json + +logger = logging.getLogger(__name__) + + +def store_backtest_result(recordfilename: Path, results: DataFrame, + strategyname: Optional[str] = None) -> None: + + records = [(t.pair, t.profit_percent, t.open_time.timestamp(), + t.close_time.timestamp(), t.open_index - 1, t.trade_duration, + t.open_rate, t.close_rate, t.open_at_end, t.sell_reason.value) + for index, t in results.iterrows()] + + if records: + if strategyname: + # Inject strategyname to filename + recordfilename = Path.joinpath( + recordfilename.parent, + f'{recordfilename.stem}-{strategyname}').with_suffix(recordfilename.suffix) + logger.info(f'Dumping backtest results to {recordfilename}') + file_dump_json(recordfilename, records) + def generate_text_table(data: Dict[str, Dict], stake_currency: str, max_open_trades: int, results: DataFrame, skip_nan: bool = False) -> str: From a13d581658173fb764c110ae9966767f17c85e7d Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 15 Mar 2020 15:17:53 +0100 Subject: [PATCH 163/202] Move backtest-result visualization out of backtesting class --- freqtrade/optimize/backtesting.py | 48 ++------------------------ freqtrade/optimize/optimize_reports.py | 44 +++++++++++++++++++++++ 2 files changed, 47 insertions(+), 45 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 9dd1b8429..323331bc6 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -18,10 +18,7 @@ from freqtrade.data.converter import trim_dataframe from freqtrade.data.dataprovider import DataProvider from freqtrade.exceptions import OperationalException from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds -from freqtrade.misc import file_dump_json -from freqtrade.optimize.optimize_reports import ( - generate_text_table, generate_text_table_sell_reason, - generate_text_table_strategy) +from freqtrade.optimize.optimize_reports import show_backtest_results from freqtrade.persistence import Trade from freqtrade.resolvers import ExchangeResolver, StrategyResolver from freqtrade.state import RunMode @@ -399,44 +396,5 @@ class Backtesting: max_open_trades=max_open_trades, position_stacking=position_stacking, ) - - for strategy, results in all_results.items(): - - if self.config.get('export', False): - self._store_backtest_result(self.config['exportfilename'], results, - strategy if len(self.strategylist) > 1 else None) - - print(f"Result for strategy {strategy}") - table = generate_text_table(data, stake_currency=self.config['stake_currency'], - max_open_trades=self.config['max_open_trades'], - results=results) - if isinstance(table, str): - print(' BACKTESTING REPORT '.center(len(table.splitlines()[0]), '=')) - print(table) - - table = generate_text_table_sell_reason(stake_currency=self.config['stake_currency'], - max_open_trades=self.config['max_open_trades'], - results=results) - if isinstance(table, str): - print(' SELL REASON STATS '.center(len(table.splitlines()[0]), '=')) - print(table) - - table = generate_text_table(data, - stake_currency=self.config['stake_currency'], - max_open_trades=self.config['max_open_trades'], - results=results.loc[results.open_at_end], skip_nan=True) - if isinstance(table, str): - print(' LEFT OPEN TRADES REPORT '.center(len(table.splitlines()[0]), '=')) - print(table) - if isinstance(table, str): - print('=' * len(table.splitlines()[0])) - print() - if len(all_results) > 1: - # Print Strategy summary table - table = generate_text_table_strategy(self.config['stake_currency'], - self.config['max_open_trades'], - all_results=all_results) - print(' STRATEGY SUMMARY '.center(len(table.splitlines()[0]), '=')) - print(table) - print('=' * len(table.splitlines()[0])) - print('\nFor more details, please look at the detail tables above') + # Show backtest results + show_backtest_results(self.config, data, all_results) diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index fb407f681..fef0accb0 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -197,3 +197,47 @@ def generate_edge_table(results: dict) -> str: # Ignore type as floatfmt does allow tuples but mypy does not know that return tabulate(tabular_data, headers=headers, floatfmt=floatfmt, tablefmt="orgtbl", stralign="right") # type: ignore + + +def show_backtest_results(config: Dict, btdata: Dict[str, DataFrame], + all_results: Dict[str, DataFrame]): + for strategy, results in all_results.items(): + + if config.get('export', False): + store_backtest_result(config['exportfilename'], results, + strategy if len(all_results) > 1 else None) + + print(f"Result for strategy {strategy}") + table = generate_text_table(btdata, stake_currency=config['stake_currency'], + max_open_trades=config['max_open_trades'], + results=results) + if isinstance(table, str): + print(' BACKTESTING REPORT '.center(len(table.splitlines()[0]), '=')) + print(table) + + table = generate_text_table_sell_reason(stake_currency=config['stake_currency'], + max_open_trades=config['max_open_trades'], + results=results) + if isinstance(table, str): + print(' SELL REASON STATS '.center(len(table.splitlines()[0]), '=')) + print(table) + + table = generate_text_table(btdata, + stake_currency=config['stake_currency'], + max_open_trades=config['max_open_trades'], + results=results.loc[results.open_at_end], skip_nan=True) + if isinstance(table, str): + print(' LEFT OPEN TRADES REPORT '.center(len(table.splitlines()[0]), '=')) + print(table) + if isinstance(table, str): + print('=' * len(table.splitlines()[0])) + print() + if len(all_results) > 1: + # Print Strategy summary table + table = generate_text_table_strategy(config['stake_currency'], + config['max_open_trades'], + all_results=all_results) + print(' STRATEGY SUMMARY '.center(len(table.splitlines()[0]), '=')) + print(table) + print('=' * len(table.splitlines()[0])) + print('\nFor more details, please look at the detail tables above') From e95665cecaeec2337dee47660674401535b406a7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 15 Mar 2020 15:36:23 +0100 Subject: [PATCH 164/202] Make backtestresult storing independent from printing --- freqtrade/optimize/backtesting.py | 6 ++++- freqtrade/optimize/optimize_reports.py | 36 ++++++++++++-------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 323331bc6..1725a7d13 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -18,7 +18,8 @@ from freqtrade.data.converter import trim_dataframe from freqtrade.data.dataprovider import DataProvider from freqtrade.exceptions import OperationalException from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds -from freqtrade.optimize.optimize_reports import show_backtest_results +from freqtrade.optimize.optimize_reports import (show_backtest_results, + store_backtest_result) from freqtrade.persistence import Trade from freqtrade.resolvers import ExchangeResolver, StrategyResolver from freqtrade.state import RunMode @@ -396,5 +397,8 @@ class Backtesting: max_open_trades=max_open_trades, position_stacking=position_stacking, ) + + if self.config.get('export', False): + store_backtest_result(self.config['exportfilename'], all_results) # Show backtest results show_backtest_results(self.config, data, all_results) diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index fef0accb0..701432071 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -11,22 +11,22 @@ from freqtrade.misc import file_dump_json logger = logging.getLogger(__name__) -def store_backtest_result(recordfilename: Path, results: DataFrame, - strategyname: Optional[str] = None) -> None: +def store_backtest_result(recordfilename: Path, all_results: Dict[str, DataFrame]) -> None: - records = [(t.pair, t.profit_percent, t.open_time.timestamp(), - t.close_time.timestamp(), t.open_index - 1, t.trade_duration, - t.open_rate, t.close_rate, t.open_at_end, t.sell_reason.value) - for index, t in results.iterrows()] + for strategy, results in all_results.items(): + records = [(t.pair, t.profit_percent, t.open_time.timestamp(), + t.close_time.timestamp(), t.open_index - 1, t.trade_duration, + t.open_rate, t.close_rate, t.open_at_end, t.sell_reason.value) + for index, t in results.iterrows()] - if records: - if strategyname: - # Inject strategyname to filename - recordfilename = Path.joinpath( - recordfilename.parent, - f'{recordfilename.stem}-{strategyname}').with_suffix(recordfilename.suffix) - logger.info(f'Dumping backtest results to {recordfilename}') - file_dump_json(recordfilename, records) + if records: + if len(all_results) > 1: + # Inject strategy to filename + recordfilename = Path.joinpath( + recordfilename.parent, + f'{recordfilename.stem}-{strategy}').with_suffix(recordfilename.suffix) + logger.info(f'Dumping backtest results to {recordfilename}') + file_dump_json(recordfilename, records) def generate_text_table(data: Dict[str, Dict], stake_currency: str, max_open_trades: int, @@ -203,10 +203,6 @@ def show_backtest_results(config: Dict, btdata: Dict[str, DataFrame], all_results: Dict[str, DataFrame]): for strategy, results in all_results.items(): - if config.get('export', False): - store_backtest_result(config['exportfilename'], results, - strategy if len(all_results) > 1 else None) - print(f"Result for strategy {strategy}") table = generate_text_table(btdata, stake_currency=config['stake_currency'], max_open_trades=config['max_open_trades'], @@ -235,8 +231,8 @@ def show_backtest_results(config: Dict, btdata: Dict[str, DataFrame], if len(all_results) > 1: # Print Strategy summary table table = generate_text_table_strategy(config['stake_currency'], - config['max_open_trades'], - all_results=all_results) + config['max_open_trades'], + all_results=all_results) print(' STRATEGY SUMMARY '.center(len(table.splitlines()[0]), '=')) print(table) print('=' * len(table.splitlines()[0])) From fe50a0f3a13e0d0f92084314af081ce1230acaac Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 15 Mar 2020 15:36:53 +0100 Subject: [PATCH 165/202] Move test for store_bt_results to optimize_reports --- tests/optimize/test_backtesting.py | 89 +++---------------------- tests/optimize/test_optimize_reports.py | 76 ++++++++++++++++++++- 2 files changed, 83 insertions(+), 82 deletions(-) diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index da23a9af4..1c4d3b16a 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -331,8 +331,8 @@ def test_backtesting_start(default_conf, mocker, testdatadir, caplog) -> None: mocker.patch('freqtrade.data.history.get_timerange', get_timerange) patch_exchange(mocker) - mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', MagicMock()) - mocker.patch('freqtrade.optimize.backtesting.generate_text_table', MagicMock(return_value=1)) + mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest') + mocker.patch('freqtrade.optimize.backtesting.show_backtest_results') default_conf['exchange']['pair_whitelist'] = ['UNITTEST/BTC'] default_conf['ticker_interval'] = '1m' @@ -361,8 +361,8 @@ def test_backtesting_start_no_data(default_conf, mocker, caplog, testdatadir) -> MagicMock(return_value=pd.DataFrame())) mocker.patch('freqtrade.data.history.get_timerange', get_timerange) patch_exchange(mocker) - mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', MagicMock()) - mocker.patch('freqtrade.optimize.backtesting.generate_text_table', MagicMock(return_value=1)) + mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest') + mocker.patch('freqtrade.optimize.backtesting.show_backtest_results') default_conf['exchange']['pair_whitelist'] = ['UNITTEST/BTC'] default_conf['ticker_interval'] = "1m" @@ -507,7 +507,6 @@ def test_backtest_only_sell(mocker, default_conf, testdatadir): def test_backtest_alternate_buy_sell(default_conf, fee, mocker, testdatadir): mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) - mocker.patch('freqtrade.optimize.backtesting.file_dump_json', MagicMock()) backtest_conf = _make_backtest_conf(mocker, conf=default_conf, pair='UNITTEST/BTC', datadir=testdatadir) default_conf['ticker_interval'] = '1m' @@ -515,7 +514,6 @@ def test_backtest_alternate_buy_sell(default_conf, fee, mocker, testdatadir): backtesting.strategy.advise_buy = _trend_alternate # Override backtesting.strategy.advise_sell = _trend_alternate # Override results = backtesting.backtest(**backtest_conf) - backtesting._store_backtest_result("test_.json", results) # 200 candles in backtest data # won't buy on first (shifted by 1) # 100 buys signals @@ -586,84 +584,12 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir) assert len(evaluate_result_multi(results, '5m', 1)) == 0 -def test_backtest_record(default_conf, fee, mocker): - names = [] - records = [] - patch_exchange(mocker) - mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) - mocker.patch( - 'freqtrade.optimize.backtesting.file_dump_json', - new=lambda n, r: (names.append(n), records.append(r)) - ) - - backtesting = Backtesting(default_conf) - results = pd.DataFrame({"pair": ["UNITTEST/BTC", "UNITTEST/BTC", - "UNITTEST/BTC", "UNITTEST/BTC"], - "profit_percent": [0.003312, 0.010801, 0.013803, 0.002780], - "profit_abs": [0.000003, 0.000011, 0.000014, 0.000003], - "open_time": [Arrow(2017, 11, 14, 19, 32, 00).datetime, - Arrow(2017, 11, 14, 21, 36, 00).datetime, - Arrow(2017, 11, 14, 22, 12, 00).datetime, - Arrow(2017, 11, 14, 22, 44, 00).datetime], - "close_time": [Arrow(2017, 11, 14, 21, 35, 00).datetime, - Arrow(2017, 11, 14, 22, 10, 00).datetime, - Arrow(2017, 11, 14, 22, 43, 00).datetime, - Arrow(2017, 11, 14, 22, 58, 00).datetime], - "open_rate": [0.002543, 0.003003, 0.003089, 0.003214], - "close_rate": [0.002546, 0.003014, 0.003103, 0.003217], - "open_index": [1, 119, 153, 185], - "close_index": [118, 151, 184, 199], - "trade_duration": [123, 34, 31, 14], - "open_at_end": [False, False, False, True], - "sell_reason": [SellType.ROI, SellType.STOP_LOSS, - SellType.ROI, SellType.FORCE_SELL] - }) - backtesting._store_backtest_result("backtest-result.json", results) - assert len(results) == 4 - # Assert file_dump_json was only called once - assert names == ['backtest-result.json'] - records = records[0] - # Ensure records are of correct type - assert len(records) == 4 - - # reset test to test with strategy name - names = [] - records = [] - backtesting._store_backtest_result(Path("backtest-result.json"), results, "DefStrat") - assert len(results) == 4 - # Assert file_dump_json was only called once - assert names == [Path('backtest-result-DefStrat.json')] - records = records[0] - # Ensure records are of correct type - assert len(records) == 4 - - # ('UNITTEST/BTC', 0.00331158, '1510684320', '1510691700', 0, 117) - # Below follows just a typecheck of the schema/type of trade-records - oix = None - for (pair, profit, date_buy, date_sell, buy_index, dur, - openr, closer, open_at_end, sell_reason) in records: - assert pair == 'UNITTEST/BTC' - assert isinstance(profit, float) - # FIX: buy/sell should be converted to ints - assert isinstance(date_buy, float) - assert isinstance(date_sell, float) - assert isinstance(openr, float) - assert isinstance(closer, float) - assert isinstance(open_at_end, bool) - assert isinstance(sell_reason, str) - isinstance(buy_index, pd._libs.tslib.Timestamp) - if oix: - assert buy_index > oix - oix = buy_index - assert dur > 0 - - def test_backtest_start_timerange(default_conf, mocker, caplog, testdatadir): default_conf['exchange']['pair_whitelist'] = ['UNITTEST/BTC'] patch_exchange(mocker) mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', MagicMock()) - mocker.patch('freqtrade.optimize.backtesting.generate_text_table', MagicMock()) + mocker.patch('freqtrade.optimize.backtesting.show_backtest_results', MagicMock()) patched_configuration_load_config_file(mocker, default_conf) @@ -705,9 +631,10 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir): backtestmock = MagicMock() mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', backtestmock) gen_table_mock = MagicMock() - mocker.patch('freqtrade.optimize.backtesting.generate_text_table', gen_table_mock) + mocker.patch('freqtrade.optimize.optimize_reports.generate_text_table', gen_table_mock) gen_strattable_mock = MagicMock() - mocker.patch('freqtrade.optimize.backtesting.generate_text_table_strategy', gen_strattable_mock) + mocker.patch('freqtrade.optimize.optimize_reports.generate_text_table_strategy', + gen_strattable_mock) patched_configuration_load_config_file(mocker, default_conf) args = [ diff --git a/tests/optimize/test_optimize_reports.py b/tests/optimize/test_optimize_reports.py index 5e68a5ef8..36c9a93b3 100644 --- a/tests/optimize/test_optimize_reports.py +++ b/tests/optimize/test_optimize_reports.py @@ -1,10 +1,14 @@ +from pathlib import Path + import pandas as pd +from arrow import Arrow from freqtrade.edge import PairInfo from freqtrade.optimize.optimize_reports import ( generate_edge_table, generate_text_table, generate_text_table_sell_reason, - generate_text_table_strategy) + generate_text_table_strategy, store_backtest_result) from freqtrade.strategy.interface import SellType +from tests.conftest import patch_exchange def test_generate_text_table(default_conf, mocker): @@ -113,3 +117,73 @@ def test_generate_edge_table(edge_conf, mocker): assert generate_edge_table(results).count('| ETH/BTC |') == 1 assert generate_edge_table(results).count( '| Risk Reward Ratio | Required Risk Reward | Expectancy |') == 1 + + +def test_backtest_record(default_conf, fee, mocker): + names = [] + records = [] + patch_exchange(mocker) + mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) + mocker.patch( + 'freqtrade.optimize.optimize_reports.file_dump_json', + new=lambda n, r: (names.append(n), records.append(r)) + ) + + results = {'DefStrat': pd.DataFrame({"pair": ["UNITTEST/BTC", "UNITTEST/BTC", + "UNITTEST/BTC", "UNITTEST/BTC"], + "profit_percent": [0.003312, 0.010801, 0.013803, 0.002780], + "profit_abs": [0.000003, 0.000011, 0.000014, 0.000003], + "open_time": [Arrow(2017, 11, 14, 19, 32, 00).datetime, + Arrow(2017, 11, 14, 21, 36, 00).datetime, + Arrow(2017, 11, 14, 22, 12, 00).datetime, + Arrow(2017, 11, 14, 22, 44, 00).datetime], + "close_time": [Arrow(2017, 11, 14, 21, 35, 00).datetime, + Arrow(2017, 11, 14, 22, 10, 00).datetime, + Arrow(2017, 11, 14, 22, 43, 00).datetime, + Arrow(2017, 11, 14, 22, 58, 00).datetime], + "open_rate": [0.002543, 0.003003, 0.003089, 0.003214], + "close_rate": [0.002546, 0.003014, 0.003103, 0.003217], + "open_index": [1, 119, 153, 185], + "close_index": [118, 151, 184, 199], + "trade_duration": [123, 34, 31, 14], + "open_at_end": [False, False, False, True], + "sell_reason": [SellType.ROI, SellType.STOP_LOSS, + SellType.ROI, SellType.FORCE_SELL] + })} + store_backtest_result(Path("backtest-result.json"), results) + # Assert file_dump_json was only called once + assert names == [Path('backtest-result.json')] + records = records[0] + # Ensure records are of correct type + assert len(records) == 4 + + # reset test to test with strategy name + names = [] + records = [] + results['Strat'] = pd.DataFrame() + store_backtest_result(Path("backtest-result.json"), results) + # Assert file_dump_json was only called once + assert names == [Path('backtest-result-DefStrat.json')] + records = records[0] + # Ensure records are of correct type + assert len(records) == 4 + + # ('UNITTEST/BTC', 0.00331158, '1510684320', '1510691700', 0, 117) + # Below follows just a typecheck of the schema/type of trade-records + oix = None + for (pair, profit, date_buy, date_sell, buy_index, dur, + openr, closer, open_at_end, sell_reason) in records: + assert pair == 'UNITTEST/BTC' + assert isinstance(profit, float) + # FIX: buy/sell should be converted to ints + assert isinstance(date_buy, float) + assert isinstance(date_sell, float) + assert isinstance(openr, float) + assert isinstance(closer, float) + assert isinstance(open_at_end, bool) + assert isinstance(sell_reason, str) + isinstance(buy_index, pd._libs.tslib.Timestamp) + if oix: + assert buy_index > oix + oix = buy_index + assert dur > 0 From e1b08ad76caf8482ee712d782bad482c643b0307 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 15 Mar 2020 15:38:26 +0100 Subject: [PATCH 166/202] Add docstring to store_backtest_result --- freqtrade/optimize/optimize_reports.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index 701432071..abfbaf1d8 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -12,7 +12,11 @@ logger = logging.getLogger(__name__) def store_backtest_result(recordfilename: Path, all_results: Dict[str, DataFrame]) -> None: - + """ + Stores backtest results to file (one file per strategy) + :param recordfilename: Destination filename + :param all_results: Dict of Dataframes, one results dataframe per strategy + """ for strategy, results in all_results.items(): records = [(t.pair, t.profit_percent, t.open_time.timestamp(), t.close_time.timestamp(), t.open_index - 1, t.trade_duration, From 3d4664c2a6da70027189d73a04b75bdd1821f79e Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 15 Mar 2020 15:40:12 +0100 Subject: [PATCH 167/202] Remove unnecessary import --- freqtrade/optimize/optimize_reports.py | 2 +- tests/optimize/test_optimize_reports.py | 40 ++++++++++++------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index abfbaf1d8..251da9159 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -1,7 +1,7 @@ import logging from datetime import timedelta from pathlib import Path -from typing import Dict, Optional +from typing import Dict from pandas import DataFrame from tabulate import tabulate diff --git a/tests/optimize/test_optimize_reports.py b/tests/optimize/test_optimize_reports.py index 36c9a93b3..f19668459 100644 --- a/tests/optimize/test_optimize_reports.py +++ b/tests/optimize/test_optimize_reports.py @@ -130,26 +130,26 @@ def test_backtest_record(default_conf, fee, mocker): ) results = {'DefStrat': pd.DataFrame({"pair": ["UNITTEST/BTC", "UNITTEST/BTC", - "UNITTEST/BTC", "UNITTEST/BTC"], - "profit_percent": [0.003312, 0.010801, 0.013803, 0.002780], - "profit_abs": [0.000003, 0.000011, 0.000014, 0.000003], - "open_time": [Arrow(2017, 11, 14, 19, 32, 00).datetime, - Arrow(2017, 11, 14, 21, 36, 00).datetime, - Arrow(2017, 11, 14, 22, 12, 00).datetime, - Arrow(2017, 11, 14, 22, 44, 00).datetime], - "close_time": [Arrow(2017, 11, 14, 21, 35, 00).datetime, - Arrow(2017, 11, 14, 22, 10, 00).datetime, - Arrow(2017, 11, 14, 22, 43, 00).datetime, - Arrow(2017, 11, 14, 22, 58, 00).datetime], - "open_rate": [0.002543, 0.003003, 0.003089, 0.003214], - "close_rate": [0.002546, 0.003014, 0.003103, 0.003217], - "open_index": [1, 119, 153, 185], - "close_index": [118, 151, 184, 199], - "trade_duration": [123, 34, 31, 14], - "open_at_end": [False, False, False, True], - "sell_reason": [SellType.ROI, SellType.STOP_LOSS, - SellType.ROI, SellType.FORCE_SELL] - })} + "UNITTEST/BTC", "UNITTEST/BTC"], + "profit_percent": [0.003312, 0.010801, 0.013803, 0.002780], + "profit_abs": [0.000003, 0.000011, 0.000014, 0.000003], + "open_time": [Arrow(2017, 11, 14, 19, 32, 00).datetime, + Arrow(2017, 11, 14, 21, 36, 00).datetime, + Arrow(2017, 11, 14, 22, 12, 00).datetime, + Arrow(2017, 11, 14, 22, 44, 00).datetime], + "close_time": [Arrow(2017, 11, 14, 21, 35, 00).datetime, + Arrow(2017, 11, 14, 22, 10, 00).datetime, + Arrow(2017, 11, 14, 22, 43, 00).datetime, + Arrow(2017, 11, 14, 22, 58, 00).datetime], + "open_rate": [0.002543, 0.003003, 0.003089, 0.003214], + "close_rate": [0.002546, 0.003014, 0.003103, 0.003217], + "open_index": [1, 119, 153, 185], + "close_index": [118, 151, 184, 199], + "trade_duration": [123, 34, 31, 14], + "open_at_end": [False, False, False, True], + "sell_reason": [SellType.ROI, SellType.STOP_LOSS, + SellType.ROI, SellType.FORCE_SELL] + })} store_backtest_result(Path("backtest-result.json"), results) # Assert file_dump_json was only called once assert names == [Path('backtest-result.json')] From 8c33e07dc660853eee0eee0d10109063f52d11cf Mon Sep 17 00:00:00 2001 From: Fredrik81 Date: Sun, 15 Mar 2020 21:20:32 +0100 Subject: [PATCH 168/202] Update based on comments --- docs/plotting.md | 2 +- freqtrade/commands/arguments.py | 2 +- freqtrade/commands/cli_options.py | 6 +++--- freqtrade/data/btanalysis.py | 6 +++--- freqtrade/plot/plotting.py | 13 ++++++------- tests/data/test_btanalysis.py | 6 +++--- 6 files changed, 17 insertions(+), 18 deletions(-) diff --git a/docs/plotting.md b/docs/plotting.md index ef28f0fe3..56906ebec 100644 --- a/docs/plotting.md +++ b/docs/plotting.md @@ -68,7 +68,7 @@ optional arguments: -i TICKER_INTERVAL, --ticker-interval TICKER_INTERVAL Specify ticker interval (`1m`, `5m`, `30m`, `1h`, `1d`). - --skip-trades Skip using trades file from backtesting and DB. + --no-trades Skip using trades from backtesting file and DB. Common arguments: -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). diff --git a/freqtrade/commands/arguments.py b/freqtrade/commands/arguments.py index 66fa0b038..8c64c5857 100644 --- a/freqtrade/commands/arguments.py +++ b/freqtrade/commands/arguments.py @@ -59,7 +59,7 @@ ARGS_DOWNLOAD_DATA = ["pairs", "pairs_file", "days", "download_trades", "exchang ARGS_PLOT_DATAFRAME = ["pairs", "indicators1", "indicators2", "plot_limit", "db_url", "trade_source", "export", "exportfilename", - "timerange", "ticker_interval", "skip_trades"] + "timerange", "ticker_interval", "no_trades"] ARGS_PLOT_PROFIT = ["pairs", "timerange", "export", "exportfilename", "db_url", "trade_source", "ticker_interval"] diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py index 187e0a424..5cf1b7fce 100644 --- a/freqtrade/commands/cli_options.py +++ b/freqtrade/commands/cli_options.py @@ -413,9 +413,9 @@ AVAILABLE_CLI_OPTIONS = { metavar='INT', default=750, ), - "skip_trades": Arg( - '--skip-trades', - help='Skip using trades file from backtesting and DB.', + "no_trades": Arg( + '--no-trades', + help='Skip using trades from backtesting file and DB.', action='store_true', ), "trade_source": Arg( diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index 8670f30c6..17b243f56 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -129,7 +129,7 @@ def load_trades_from_db(db_url: str) -> pd.DataFrame: return trades -def load_trades(source: str, db_url: str, exportfilename: Path, skip_trades: bool) -> pd.DataFrame: +def load_trades(source: str, db_url: str, exportfilename: Path, no_trades: bool) -> pd.DataFrame: """ Based on configuration option "trade_source": * loads data from DB (using `db_url`) @@ -137,10 +137,10 @@ def load_trades(source: str, db_url: str, exportfilename: Path, skip_trades: boo :param source: "DB" or "file" - specify source to load from :param db_url: sqlalchemy formatted url to a database :param exportfilename: Json file generated by backtesting - :param skip_trades: Skip using trades, only return backtesting data columns + :param no_trades: Skip using trades, only return backtesting data columns :return: DataFrame containing trades """ - if skip_trades: + if no_trades: df = pd.DataFrame(columns=BT_DATA_COLUMNS) return df diff --git a/freqtrade/plot/plotting.py b/freqtrade/plot/plotting.py index 8da61597f..fc8f25612 100644 --- a/freqtrade/plot/plotting.py +++ b/freqtrade/plot/plotting.py @@ -3,7 +3,6 @@ from pathlib import Path from typing import Any, Dict, List import pandas as pd -from os.path import isfile from freqtrade.configuration import TimeRange from freqtrade.data.btanalysis import (calculate_max_drawdown, @@ -49,18 +48,18 @@ def init_plotscript(config): data_format=config.get('dataformat_ohlcv', 'json'), ) - skip_trades = False - if not isfile(config.get('exportfilename')) and config['trade_source'] == 'file': + no_trades = False + if config.get('no_trades', False): + no_trades = True + elif not config['exportfilename'].is_file() and config['trade_source'] == 'file': logger.warning("Backtest file is missing skipping trades.") - skip_trades = True - elif config.get('skip_trades', False): - skip_trades = True + no_trades = True trades = load_trades( config['trade_source'], db_url=config.get('db_url'), exportfilename=config.get('exportfilename'), - skip_trades=skip_trades + no_trades=no_trades ) trades = trim_dataframe(trades, timerange, 'open_time') diff --git a/tests/data/test_btanalysis.py b/tests/data/test_btanalysis.py index 8561f2b84..4b47faeee 100644 --- a/tests/data/test_btanalysis.py +++ b/tests/data/test_btanalysis.py @@ -105,7 +105,7 @@ def test_load_trades(default_conf, mocker): load_trades("DB", db_url=default_conf.get('db_url'), exportfilename=default_conf.get('exportfilename'), - skip_trades=False + no_trades=False ) assert db_mock.call_count == 1 @@ -117,7 +117,7 @@ def test_load_trades(default_conf, mocker): load_trades("file", db_url=default_conf.get('db_url'), exportfilename=default_conf.get('exportfilename'), - skip_trades=False + no_trades=False ) assert db_mock.call_count == 0 @@ -129,7 +129,7 @@ def test_load_trades(default_conf, mocker): load_trades("file", db_url=default_conf.get('db_url'), exportfilename=default_conf.get('exportfilename'), - skip_trades=True + no_trades=True ) assert db_mock.call_count == 0 From 06198c00283eeaef44f0dda391a3cc769acce6ae Mon Sep 17 00:00:00 2001 From: Fredrik81 Date: Sun, 15 Mar 2020 21:27:45 +0100 Subject: [PATCH 169/202] Missed configuration.py --- freqtrade/configuration/configuration.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/freqtrade/configuration/configuration.py b/freqtrade/configuration/configuration.py index ce2101441..e5515670d 100644 --- a/freqtrade/configuration/configuration.py +++ b/freqtrade/configuration/configuration.py @@ -359,6 +359,9 @@ class Configuration: self._args_to_config(config, argname='erase', logstring='Erase detected. Deleting existing data.') + self._args_to_config(config, argname='no_trades', + logstring='Parameter --no-trades detected.') + self._args_to_config(config, argname='timeframes', logstring='timeframes --timeframes: {}') From e8a92cb3136971bbcd96257e4dc036e7b701da26 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 16 Mar 2020 08:17:21 +0000 Subject: [PATCH 170/202] Bump pandas from 1.0.1 to 1.0.2 Bumps [pandas](https://github.com/pandas-dev/pandas) from 1.0.1 to 1.0.2. - [Release notes](https://github.com/pandas-dev/pandas/releases) - [Changelog](https://github.com/pandas-dev/pandas/blob/master/RELEASE.md) - [Commits](https://github.com/pandas-dev/pandas/compare/v1.0.1...v1.0.2) Signed-off-by: dependabot-preview[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 68024f587..12d7336aa 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,4 @@ -r requirements-common.txt numpy==1.18.1 -pandas==1.0.1 +pandas==1.0.2 From 0b341757525c23f1242e7e9b189d97b76f5b54b2 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 16 Mar 2020 08:17:53 +0000 Subject: [PATCH 171/202] Bump ccxt from 1.23.81 to 1.24.31 Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.23.81 to 1.24.31. - [Release notes](https://github.com/ccxt/ccxt/releases) - [Changelog](https://github.com/ccxt/ccxt/blob/master/doc/exchanges-by-country.rst) - [Commits](https://github.com/ccxt/ccxt/compare/1.23.81...1.24.31) Signed-off-by: dependabot-preview[bot] --- requirements-common.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-common.txt b/requirements-common.txt index 784eef93c..f3f7d8a07 100644 --- a/requirements-common.txt +++ b/requirements-common.txt @@ -1,6 +1,6 @@ # requirements without requirements installable via conda # mainly used for Raspberry pi installs -ccxt==1.23.81 +ccxt==1.24.31 SQLAlchemy==1.3.13 python-telegram-bot==12.4.2 arrow==0.15.5 From 8cfff40fcfb2ba7145c12b165f75e34debcedae6 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 16 Mar 2020 08:18:07 +0000 Subject: [PATCH 172/202] Bump plotly from 4.5.3 to 4.5.4 Bumps [plotly](https://github.com/plotly/plotly.py) from 4.5.3 to 4.5.4. - [Release notes](https://github.com/plotly/plotly.py/releases) - [Changelog](https://github.com/plotly/plotly.py/blob/master/CHANGELOG.md) - [Commits](https://github.com/plotly/plotly.py/compare/v4.5.3...v4.5.4) Signed-off-by: dependabot-preview[bot] --- requirements-plot.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-plot.txt b/requirements-plot.txt index 381334a66..7a5b21e2d 100644 --- a/requirements-plot.txt +++ b/requirements-plot.txt @@ -1,5 +1,5 @@ # Include all requirements to run the bot. -r requirements.txt -plotly==4.5.3 +plotly==4.5.4 From 3eee0c43a727fe91bad3481e1dab0551793afa4e Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 16 Mar 2020 08:18:33 +0000 Subject: [PATCH 173/202] Bump pytest from 5.3.5 to 5.4.1 Bumps [pytest](https://github.com/pytest-dev/pytest) from 5.3.5 to 5.4.1. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/master/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/5.3.5...5.4.1) Signed-off-by: dependabot-preview[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 1e58ae6e0..f2801c15b 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -8,7 +8,7 @@ flake8==3.7.9 flake8-type-annotations==0.1.0 flake8-tidy-imports==4.0.0 mypy==0.761 -pytest==5.3.5 +pytest==5.4.1 pytest-asyncio==0.10.0 pytest-cov==2.8.1 pytest-mock==2.0.0 From 7a7530d57d4ef7d39ff715ccee7cf9ddf2a820a0 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 16 Mar 2020 08:53:20 +0000 Subject: [PATCH 174/202] Bump sqlalchemy from 1.3.13 to 1.3.15 Bumps [sqlalchemy](https://github.com/sqlalchemy/sqlalchemy) from 1.3.13 to 1.3.15. - [Release notes](https://github.com/sqlalchemy/sqlalchemy/releases) - [Changelog](https://github.com/sqlalchemy/sqlalchemy/blob/master/CHANGES) - [Commits](https://github.com/sqlalchemy/sqlalchemy/commits) Signed-off-by: dependabot-preview[bot] --- requirements-common.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-common.txt b/requirements-common.txt index f3f7d8a07..7c2ad4283 100644 --- a/requirements-common.txt +++ b/requirements-common.txt @@ -1,7 +1,7 @@ # requirements without requirements installable via conda # mainly used for Raspberry pi installs ccxt==1.24.31 -SQLAlchemy==1.3.13 +SQLAlchemy==1.3.15 python-telegram-bot==12.4.2 arrow==0.15.5 cachetools==4.0.0 From 62d449251cc8a02182a46015bc2a36e71d549c85 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 16 Mar 2020 08:54:12 +0000 Subject: [PATCH 175/202] Bump mypy from 0.761 to 0.770 Bumps [mypy](https://github.com/python/mypy) from 0.761 to 0.770. - [Release notes](https://github.com/python/mypy/releases) - [Commits](https://github.com/python/mypy/compare/v0.761...v0.770) Signed-off-by: dependabot-preview[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index f2801c15b..a4d83eb4f 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,7 +7,7 @@ coveralls==1.11.1 flake8==3.7.9 flake8-type-annotations==0.1.0 flake8-tidy-imports==4.0.0 -mypy==0.761 +mypy==0.770 pytest==5.4.1 pytest-asyncio==0.10.0 pytest-cov==2.8.1 From 4f46fb9bf5cb0ae22e3b365e552f62da712bd99f Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 17 Mar 2020 19:33:18 +0100 Subject: [PATCH 176/202] Add template and jupyter files to release --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) diff --git a/MANIFEST.in b/MANIFEST.in index 7529152a0..c67f5258f 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,3 +2,4 @@ include LICENSE include README.md include config.json.example recursive-include freqtrade *.py +recursive-include freqtrade/templates/ *.j2 *.ipynb From 05250ba6611278b794677780a2e00febdb14f476 Mon Sep 17 00:00:00 2001 From: Fredrik81 Date: Wed, 18 Mar 2020 11:00:33 +0100 Subject: [PATCH 177/202] Update docs/plotting.md Co-Authored-By: Matthias --- docs/plotting.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/plotting.md b/docs/plotting.md index 56906ebec..be83065a6 100644 --- a/docs/plotting.md +++ b/docs/plotting.md @@ -32,7 +32,7 @@ usage: freqtrade plot-dataframe [-h] [-v] [--logfile FILE] [-V] [-c PATH] [--trade-source {DB,file}] [--export EXPORT] [--export-filename PATH] [--timerange TIMERANGE] [-i TICKER_INTERVAL] - [--skip-trades] + [--no-trades] optional arguments: -h, --help show this help message and exit From 0920d6fce4d35ebd3f99ed321131764fde628206 Mon Sep 17 00:00:00 2001 From: Fredrik81 Date: Wed, 18 Mar 2020 11:01:09 +0100 Subject: [PATCH 178/202] Update freqtrade/data/btanalysis.py Co-Authored-By: Matthias --- freqtrade/data/btanalysis.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index 17b243f56..898072748 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -129,7 +129,7 @@ def load_trades_from_db(db_url: str) -> pd.DataFrame: return trades -def load_trades(source: str, db_url: str, exportfilename: Path, no_trades: bool) -> pd.DataFrame: +def load_trades(source: str, db_url: str, exportfilename: Path, no_trades: bool = False) -> pd.DataFrame: """ Based on configuration option "trade_source": * loads data from DB (using `db_url`) From 3e1bef888ab1344f8763e130cdef835b0b030d6b Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 18 Mar 2020 11:42:42 +0100 Subject: [PATCH 179/202] Fix flake8 error --- freqtrade/data/btanalysis.py | 3 ++- tests/data/test_btanalysis.py | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index 898072748..23a9f720c 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -129,7 +129,8 @@ def load_trades_from_db(db_url: str) -> pd.DataFrame: return trades -def load_trades(source: str, db_url: str, exportfilename: Path, no_trades: bool = False) -> pd.DataFrame: +def load_trades(source: str, db_url: str, exportfilename: Path, + no_trades: bool = False) -> pd.DataFrame: """ Based on configuration option "trade_source": * loads data from DB (using `db_url`) diff --git a/tests/data/test_btanalysis.py b/tests/data/test_btanalysis.py index 4b47faeee..463e5ae36 100644 --- a/tests/data/test_btanalysis.py +++ b/tests/data/test_btanalysis.py @@ -117,7 +117,6 @@ def test_load_trades(default_conf, mocker): load_trades("file", db_url=default_conf.get('db_url'), exportfilename=default_conf.get('exportfilename'), - no_trades=False ) assert db_mock.call_count == 0 From ecf3a3e07076e3101e804e5216ec38ff236a1686 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 19 Mar 2020 19:42:51 +0100 Subject: [PATCH 180/202] Add test validating different return values --- tests/test_freqtradebot.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 9f6e5ef0c..e37270bd3 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -2228,10 +2228,16 @@ def test_handle_timedout_limit_buy(mocker, default_conf, limit_buy_order) -> Non assert cancel_order_mock.call_count == 1 -def test_handle_timedout_limit_buy_corder_empty(mocker, default_conf, limit_buy_order) -> None: +@pytest.mark.parametrize('cancelorder', [ + {}, + 'String Return value', + 123 +]) +def test_handle_timedout_limit_buy_corder_empty(mocker, default_conf, limit_buy_order, + cancelorder) -> None: patch_RPCManager(mocker) patch_exchange(mocker) - cancel_order_mock = MagicMock(return_value={}) + cancel_order_mock = MagicMock(return_value=cancelorder) mocker.patch.multiple( 'freqtrade.exchange.Exchange', cancel_order=cancel_order_mock From 5e702f6891c76a066aae85833026780cc01ebee5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 19 Mar 2020 19:43:19 +0100 Subject: [PATCH 181/202] Verify cancel_order returnvalue is a dictionary --- freqtrade/freqtradebot.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 9897b39b4..bc3078a5c 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -891,6 +891,9 @@ class FreqtradeBot: if order['status'] != 'canceled': reason = "cancelled due to timeout" corder = self.exchange.cancel_order(trade.open_order_id, trade.pair) + # Some exchanges don't return a dict here. + if not isinstance(corder, dict): + corder = {} logger.info('Buy order %s for %s.', reason, trade) else: # Order was cancelled already, so we can reuse the existing dict From 5f9479b39fe4c31ac0171501c28186ef0b1065b9 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Fri, 20 Mar 2020 02:10:44 +0300 Subject: [PATCH 182/202] Edge import cosmetics --- freqtrade/edge/edge_positioning.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/freqtrade/edge/edge_positioning.py b/freqtrade/edge/edge_positioning.py index d196ab4b3..5305e23cf 100644 --- a/freqtrade/edge/edge_positioning.py +++ b/freqtrade/edge/edge_positioning.py @@ -8,10 +8,10 @@ import numpy as np import utils_find_1st as utf1st from pandas import DataFrame -from freqtrade import constants from freqtrade.configuration import TimeRange -from freqtrade.data import history +from freqtrade.constants import UNLIMITED_STAKE_AMOUNT from freqtrade.exceptions import OperationalException +from freqtrade.data.history import get_timerange, load_data, refresh_data from freqtrade.strategy.interface import SellType logger = logging.getLogger(__name__) @@ -54,7 +54,7 @@ class Edge: if self.config['max_open_trades'] != float('inf'): logger.critical('max_open_trades should be -1 in config !') - if self.config['stake_amount'] != constants.UNLIMITED_STAKE_AMOUNT: + if self.config['stake_amount'] != UNLIMITED_STAKE_AMOUNT: raise OperationalException('Edge works only with unlimited stake amount') # Deprecated capital_available_percentage. Will use tradable_balance_ratio in the future. @@ -96,7 +96,7 @@ class Edge: logger.info('Using local backtesting data (using whitelist in given config) ...') if self._refresh_pairs: - history.refresh_data( + refresh_data( datadir=self.config['datadir'], pairs=pairs, exchange=self.exchange, @@ -104,7 +104,7 @@ class Edge: timerange=self._timerange, ) - data = history.load_data( + data = load_data( datadir=self.config['datadir'], pairs=pairs, timeframe=self.strategy.ticker_interval, @@ -122,7 +122,7 @@ class Edge: preprocessed = self.strategy.ohlcvdata_to_dataframe(data) # Print timeframe - min_date, max_date = history.get_timerange(preprocessed) + min_date, max_date = get_timerange(preprocessed) logger.info( 'Measuring data from %s up to %s (%s days) ...', min_date.isoformat(), From 3e0ffdce75aa6795caa830bfeb4c614a1750c207 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Fri, 20 Mar 2020 04:21:17 +0300 Subject: [PATCH 183/202] Adjust tests --- tests/edge/test_edge.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/edge/test_edge.py b/tests/edge/test_edge.py index 3bebeee65..2304c53c2 100644 --- a/tests/edge/test_edge.py +++ b/tests/edge/test_edge.py @@ -292,8 +292,8 @@ def mocked_load_data(datadir, pairs=[], timeframe='0m', def test_edge_process_downloaded_data(mocker, edge_conf): freqtrade = get_patched_freqtradebot(mocker, edge_conf) mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.001)) - mocker.patch('freqtrade.data.history.refresh_data', MagicMock()) - mocker.patch('freqtrade.data.history.load_data', mocked_load_data) + mocker.patch('freqtrade.edge.edge_positioning.refresh_data', MagicMock()) + mocker.patch('freqtrade.edge.edge_positioning.load_data', mocked_load_data) edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy) assert edge.calculate() @@ -304,8 +304,8 @@ def test_edge_process_downloaded_data(mocker, edge_conf): def test_edge_process_no_data(mocker, edge_conf, caplog): freqtrade = get_patched_freqtradebot(mocker, edge_conf) mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.001)) - mocker.patch('freqtrade.data.history.refresh_data', MagicMock()) - mocker.patch('freqtrade.data.history.load_data', MagicMock(return_value={})) + mocker.patch('freqtrade.edge.edge_positioning.refresh_data', MagicMock()) + mocker.patch('freqtrade.edge.edge_positioning.load_data', MagicMock(return_value={})) edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy) assert not edge.calculate() @@ -317,8 +317,8 @@ def test_edge_process_no_data(mocker, edge_conf, caplog): def test_edge_process_no_trades(mocker, edge_conf, caplog): freqtrade = get_patched_freqtradebot(mocker, edge_conf) mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.001)) - mocker.patch('freqtrade.data.history.refresh_data', MagicMock()) - mocker.patch('freqtrade.data.history.load_data', mocked_load_data) + mocker.patch('freqtrade.edge.edge_positioning.refresh_data', MagicMock()) + mocker.patch('freqtrade.edge.edge_positioning.load_data', mocked_load_data) # Return empty mocker.patch('freqtrade.edge.Edge._find_trades_for_stoploss_range', MagicMock(return_value=[])) edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy) From 942792f1233f35eba31d51d96ecbc92163376af1 Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Fri, 20 Mar 2020 05:48:53 +0100 Subject: [PATCH 184/202] updated as suggested --- freqtrade/freqtradebot.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index e5ae9043a..378252c29 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -394,21 +394,23 @@ class FreqtradeBot: logger.info(f"Pair {pair} is currently locked.") return False + # get_free_open_trades is checked before create_trade is called + # but it is still used here to prevent opening too many trades within one iteration if not self.get_free_open_trades(): logger.debug(f"Can't open a new trade for {pair}: max number of trades is reached.") return False - stake_amount = self.get_trade_stake_amount(pair) - if not stake_amount: - logger.debug(f"Stake amount is 0, ignoring possible trade for {pair}.") - return False - # running get_signal on historical data fetched (buy, sell) = self.strategy.get_signal( pair, self.strategy.ticker_interval, self.dataprovider.ohlcv(pair, self.strategy.ticker_interval)) if buy and not sell: + stake_amount = self.get_trade_stake_amount(pair) + if not stake_amount: + logger.debug(f"Stake amount is 0, ignoring possible trade for {pair}.") + return False + logger.info(f"Buy signal found: about create a new trade with stake_amount: " f"{stake_amount} ...") From e30faf8c8c371de43b0e1c35b5eae89489e01ab9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 21 Mar 2020 20:04:05 +0100 Subject: [PATCH 185/202] Remove partial candle documentation It wasn't working 100% correctly - see #2993 --- docs/exchanges.md | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/docs/exchanges.md b/docs/exchanges.md index 66a0e96da..c600077ce 100644 --- a/docs/exchanges.md +++ b/docs/exchanges.md @@ -74,23 +74,13 @@ Should you experience constant errors with Nonce (like `InvalidNonce`), it is be $ pip3 install web3 ``` -### Send incomplete candles to the strategy +### Getting latest price / Incomplete candles Most exchanges return current incomplete candle via their OHLCV/klines API interface. By default, Freqtrade assumes that incomplete candle is fetched from the exchange and removes the last candle assuming it's the incomplete candle. Whether your exchange returns incomplete candles or not can be checked using [the helper script](developer.md#Incomplete-candles) from the Contributor documentation. -If the exchange does return incomplete candles and you would like to have incomplete candles in your strategy, you can set the following parameter in the configuration file. +Due to the danger of repainting, Freqtrade does not allow you to use this incomplete candle. -``` json -{ - - "exchange": { - "_ft_has_params": {"ohlcv_partial_candle": false} - } -} -``` - -!!! Warning "Danger of repainting" - Changing this parameter makes the strategy responsible to avoid repainting and handle this accordingly. Doing this is therefore not recommended, and should only be performed by experienced users who are fully aware of the impact this setting has. +However, usually, this requirement is based on the need for the latest price - which can be aquired using the [data provider](strategy-customization.md#possible-options-for-dataprovider) from within the strategy. From 2c434e9b116fdfb71c2da4f57db4e25683ce7d39 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 22 Mar 2020 11:16:09 +0100 Subject: [PATCH 186/202] Add close_proit_abs column --- freqtrade/freqtradebot.py | 1 + freqtrade/persistence.py | 11 ++++++++--- tests/test_persistence.py | 20 ++++++++++++++++++++ 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index db632693a..570f8bea8 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -954,6 +954,7 @@ class FreqtradeBot: trade.close_rate = None trade.close_profit = None + trade.close_profit_abs = None trade.close_date = None trade.is_open = True trade.open_order_id = None diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index ac084d12e..0d668596c 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -86,7 +86,7 @@ def check_migrate(engine) -> None: logger.debug(f'trying {table_back_name}') # Check for latest column - if not has_column(cols, 'open_trade_price'): + if not has_column(cols, 'close_profit_abs'): logger.info(f'Running database migration - backup available as {table_back_name}') fee_open = get_column_def(cols, 'fee_open', 'fee') @@ -106,6 +106,9 @@ def check_migrate(engine) -> None: ticker_interval = get_column_def(cols, 'ticker_interval', 'null') open_trade_price = get_column_def(cols, 'open_trade_price', f'amount * open_rate * (1 + {fee_open})') + close_profit_abs = get_column_def( + cols, 'close_profit_abs', + f"(amount * close_rate * (1 - {fee_close})) - {open_trade_price}") # Schema migration necessary engine.execute(f"alter table trades rename to {table_back_name}") @@ -123,7 +126,7 @@ def check_migrate(engine) -> None: stop_loss, stop_loss_pct, initial_stop_loss, initial_stop_loss_pct, stoploss_order_id, stoploss_last_update, max_rate, min_rate, sell_reason, strategy, - ticker_interval, open_trade_price + ticker_interval, open_trade_price, close_profit_abs ) select id, lower(exchange), case @@ -143,7 +146,7 @@ def check_migrate(engine) -> None: {stoploss_order_id} stoploss_order_id, {stoploss_last_update} stoploss_last_update, {max_rate} max_rate, {min_rate} min_rate, {sell_reason} sell_reason, {strategy} strategy, {ticker_interval} ticker_interval, - {open_trade_price} open_trade_price + {open_trade_price} open_trade_price, {close_profit_abs} close_profit_abs from {table_back_name} """) @@ -190,6 +193,7 @@ class Trade(_DECL_BASE): close_rate = Column(Float) close_rate_requested = Column(Float) close_profit = Column(Float) + close_profit_abs = Column(Float) stake_amount = Column(Float, nullable=False) amount = Column(Float) open_date = Column(DateTime, nullable=False, default=datetime.utcnow) @@ -334,6 +338,7 @@ class Trade(_DECL_BASE): """ self.close_rate = Decimal(rate) self.close_profit = self.calc_profit_ratio() + self.close_profit_abs = self.calc_profit() self.close_date = datetime.utcnow() self.is_open = False self.open_order_id = None diff --git a/tests/test_persistence.py b/tests/test_persistence.py index 6bd7971a7..991922cba 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -476,12 +476,22 @@ def test_migrate_old(mocker, default_conf, fee): stake=default_conf.get("stake_amount"), amount=amount ) + insert_table_old2 = """INSERT INTO trades (exchange, pair, is_open, fee, + open_rate, close_rate, stake_amount, amount, open_date) + VALUES ('BITTREX', 'BTC_ETC', 0, {fee}, + 0.00258580, 0.00268580, {stake}, {amount}, + '2017-11-28 12:44:24.000000') + """.format(fee=fee.return_value, + stake=default_conf.get("stake_amount"), + amount=amount + ) engine = create_engine('sqlite://') mocker.patch('freqtrade.persistence.create_engine', lambda *args, **kwargs: engine) # Create table using the old format engine.execute(create_table_old) engine.execute(insert_table_old) + engine.execute(insert_table_old2) # Run init to test migration init(default_conf['db_url'], default_conf['dry_run']) @@ -500,6 +510,15 @@ def test_migrate_old(mocker, default_conf, fee): assert trade.stop_loss == 0.0 assert trade.initial_stop_loss == 0.0 assert trade.open_trade_price == trade._calc_open_trade_price() + assert trade.close_profit_abs is None + + trade = Trade.query.filter(Trade.id == 2).first() + assert trade.close_rate is not None + assert trade.is_open == 0 + assert trade.open_rate_requested is None + assert trade.close_rate_requested is None + assert trade.close_rate is not None + assert pytest.approx(trade.close_profit_abs) == trade.calc_profit() def test_migrate_new(mocker, default_conf, fee, caplog): @@ -583,6 +602,7 @@ def test_migrate_new(mocker, default_conf, fee, caplog): assert log_has("trying trades_bak2", caplog) assert log_has("Running database migration - backup available as trades_bak2", caplog) assert trade.open_trade_price == trade._calc_open_trade_price() + assert trade.close_profit_abs is None def test_migrate_mid_state(mocker, default_conf, fee, caplog): From efd94c84debab78c00bc141b6108f43b366c2e07 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 22 Mar 2020 11:22:49 +0100 Subject: [PATCH 187/202] Add example notebook to gitignore again --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 9ac2c9d5d..f206fce66 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,6 @@ user_data/* !user_data/strategy/sample_strategy.py !user_data/notebooks user_data/notebooks/* -!user_data/notebooks/*example.ipynb freqtrade-plot.html freqtrade-profit-plot.html From f14c496ce9ffca5186d4479a611c37c76c0a35ce Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 22 Mar 2020 11:28:18 +0100 Subject: [PATCH 188/202] Remove calc_close_profit from RPC This is now possible - but only for closed trades, so certain occurances need to remain. --- freqtrade/rpc/rpc.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 9014c1874..a0f50b070 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -197,7 +197,7 @@ class RPC: Trade.close_date >= profitday, Trade.close_date < (profitday + timedelta(days=1)) ]).order_by(Trade.close_date).all() - curdayprofit = sum(trade.calc_profit() for trade in trades) + curdayprofit = sum(trade.close_profit_abs for trade in trades) profit_days[profitday] = { 'amount': f'{curdayprofit:.8f}', 'trades': len(trades) @@ -246,8 +246,8 @@ class RPC: durations.append((trade.close_date - trade.open_date).total_seconds()) if not trade.is_open: - profit_ratio = trade.calc_profit_ratio() - profit_closed_coin.append(trade.calc_profit()) + profit_ratio = trade.close_profit + profit_closed_coin.append(trade.close_profit_abs) profit_closed_ratio.append(profit_ratio) else: # Get current rate From acd402187a179e1f040fc42fe1dba22b2519a2e9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 22 Mar 2020 19:39:36 +0100 Subject: [PATCH 189/202] Update max_open_trades documentation --- docs/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index b0f4c7554..78c643868 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -40,7 +40,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi | Parameter | Description | |------------|-------------| -| `max_open_trades` | **Required.** Number of trades open your bot will have. If -1 then it is ignored (i.e. potentially unlimited open trades). [More information below](#configuring-amount-per-trade).
**Datatype:** Positive integer or -1. +| `max_open_trades` | **Required.** Number of open trades your bot is allowed to have. Only one open trade per pair is possible, so the lenght of your whitelist another limitation which can apply. If -1 then it is ignored (i.e. potentially unlimited open trades). [More information below](#configuring-amount-per-trade).
**Datatype:** Positive integer or -1. | `stake_currency` | **Required.** Crypto-currency used for trading. [Strategy Override](#parameters-in-the-strategy).
**Datatype:** String | `stake_amount` | **Required.** Amount of crypto-currency your bot will use for each trade. Set it to `"unlimited"` to allow the bot to use all available balance. [More information below](#configuring-amount-per-trade). [Strategy Override](#parameters-in-the-strategy).
**Datatype:** Positive float or `"unlimited"`. | `tradable_balance_ratio` | Ratio of the total account balance the bot is allowed to trade. [More information below](#configuring-amount-per-trade).
*Defaults to `0.99` 99%).*
**Datatype:** Positive float between `0.1` and `1.0`. From 45aaa8c09d2a704e49b56ef47b360425f305b7b5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 22 Mar 2020 20:09:01 +0100 Subject: [PATCH 190/202] Parse and show relevant configuration section --- freqtrade/configuration/load_config.py | 28 ++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/freqtrade/configuration/load_config.py b/freqtrade/configuration/load_config.py index 19179c6c3..0d2dc0955 100644 --- a/freqtrade/configuration/load_config.py +++ b/freqtrade/configuration/load_config.py @@ -1,13 +1,15 @@ """ This module contain functions to load the configuration file """ -import rapidjson import logging +import re import sys +from pathlib import Path from typing import Any, Dict -from freqtrade.exceptions import OperationalException +import rapidjson +from freqtrade.exceptions import OperationalException logger = logging.getLogger(__name__) @@ -15,6 +17,22 @@ logger = logging.getLogger(__name__) CONFIG_PARSE_MODE = rapidjson.PM_COMMENTS | rapidjson.PM_TRAILING_COMMAS +def log_config_error_range(path: str, errmsg: str) -> str: + """ + Parses configuration file and prints range around error + """ + if path != '-': + offsetlist = re.findall(r'(?<=Parse\serror\sat\soffset\s)\d+', errmsg) + if offsetlist: + offset = int(offsetlist[0]) + text = Path(path).read_text() + # Fetch an offset of 80 characters around the error line + subtext = text[offset-min(80, offset):offset+80] + segments = subtext.split('\n') + # Remove first and last lines, to avoid odd truncations + return '\n'.join(segments[1:-1]) + + def load_config_file(path: str) -> Dict[str, Any]: """ Loads a config file from the given path @@ -29,5 +47,11 @@ def load_config_file(path: str) -> Dict[str, Any]: raise OperationalException( f'Config file "{path}" not found!' ' Please create a config file or check whether it exists.') + except rapidjson.JSONDecodeError as e: + err_range = log_config_error_range(path, str(e)) + raise OperationalException( + f'{e}\n' + f'Please verify the following segment of your configuration:\n{err_range}' + ) return config From 6c55b40fe04f4cea4b92841212b150d69f5d200a Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 22 Mar 2020 20:15:33 +0100 Subject: [PATCH 191/202] Add test verifying config printing --- tests/test_configuration.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 1e9d6440d..ec6753feb 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -66,6 +66,17 @@ def test_load_config_file(default_conf, mocker, caplog) -> None: assert validated_conf.items() >= default_conf.items() +def test_load_config_file_error(default_conf, mocker, caplog) -> None: + del default_conf['user_data_dir'] + filedata = json.dumps(default_conf).replace( + '"stake_amount": 0.001,', '"stake_amount": .001,') + mocker.patch('freqtrade.configuration.load_config.open', mocker.mock_open(read_data=filedata)) + mocker.patch.object(Path, "read_text", MagicMock(return_value=filedata)) + + with pytest.raises(OperationalException, match=f".*Please verify the following segment.*"): + load_config_file('somefile') + + def test__args_to_config(caplog): arg_list = ['trade', '--strategy-path', 'TestTest'] From 8f7e113d798bb8b973efd6a66b59d1abf18722f4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 23 Mar 2020 07:54:27 +0100 Subject: [PATCH 192/202] Add additional test --- freqtrade/configuration/load_config.py | 7 +++++-- tests/test_configuration.py | 15 ++++++++++++++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/freqtrade/configuration/load_config.py b/freqtrade/configuration/load_config.py index 0d2dc0955..55da28913 100644 --- a/freqtrade/configuration/load_config.py +++ b/freqtrade/configuration/load_config.py @@ -29,8 +29,11 @@ def log_config_error_range(path: str, errmsg: str) -> str: # Fetch an offset of 80 characters around the error line subtext = text[offset-min(80, offset):offset+80] segments = subtext.split('\n') - # Remove first and last lines, to avoid odd truncations - return '\n'.join(segments[1:-1]) + if len(segments) > 3: + # Remove first and last lines, to avoid odd truncations + return '\n'.join(segments[1:-1]) + else: + return subtext def load_config_file(path: str) -> Dict[str, Any]: diff --git a/tests/test_configuration.py b/tests/test_configuration.py index ec6753feb..a1936aee0 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -18,7 +18,7 @@ from freqtrade.configuration.config_validation import validate_config_schema from freqtrade.configuration.deprecated_settings import ( check_conflicting_settings, process_deprecated_setting, process_temporary_deprecated_settings) -from freqtrade.configuration.load_config import load_config_file +from freqtrade.configuration.load_config import load_config_file, log_config_error_range from freqtrade.constants import DEFAULT_DB_DRYRUN_URL, DEFAULT_DB_PROD_URL from freqtrade.exceptions import OperationalException from freqtrade.loggers import _set_loggers, setup_logging @@ -77,6 +77,19 @@ def test_load_config_file_error(default_conf, mocker, caplog) -> None: load_config_file('somefile') +def test_load_config_file_error_range(default_conf, mocker, caplog) -> None: + del default_conf['user_data_dir'] + filedata = json.dumps(default_conf).replace( + '"stake_amount": 0.001,', '"stake_amount": .001,') + mocker.patch.object(Path, "read_text", MagicMock(return_value=filedata)) + + x = log_config_error_range('somefile', 'Parse error at offset 64: Invalid value.') + assert isinstance(x, str) + assert (x == '{"max_open_trades": 1, "stake_currency": "BTC", ' + '"stake_amount": .001, "fiat_display_currency": "USD", ' + '"ticker_interval": "5m", "dry_run": true, ') + + def test__args_to_config(caplog): arg_list = ['trade', '--strategy-path', 'TestTest'] From d581b7e2d79493ddccb6cbd2cbe41b6baa028b38 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 23 Mar 2020 07:57:30 +0100 Subject: [PATCH 193/202] Add fallback if no error could be determined --- freqtrade/configuration/load_config.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/freqtrade/configuration/load_config.py b/freqtrade/configuration/load_config.py index 55da28913..a24ee3d0a 100644 --- a/freqtrade/configuration/load_config.py +++ b/freqtrade/configuration/load_config.py @@ -34,6 +34,7 @@ def log_config_error_range(path: str, errmsg: str) -> str: return '\n'.join(segments[1:-1]) else: return subtext + return '' def load_config_file(path: str) -> Dict[str, Any]: @@ -55,6 +56,7 @@ def load_config_file(path: str) -> Dict[str, Any]: raise OperationalException( f'{e}\n' f'Please verify the following segment of your configuration:\n{err_range}' + if err_range else 'Please verify your configuration file for syntax errors.' ) return config From f4a69ba5a753c38f9b18ec81e596d38c86ce66de Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 23 Mar 2020 09:04:50 +0000 Subject: [PATCH 194/202] Bump numpy from 1.18.1 to 1.18.2 Bumps [numpy](https://github.com/numpy/numpy) from 1.18.1 to 1.18.2. - [Release notes](https://github.com/numpy/numpy/releases) - [Changelog](https://github.com/numpy/numpy/blob/master/doc/HOWTO_RELEASE.rst.txt) - [Commits](https://github.com/numpy/numpy/compare/v1.18.1...v1.18.2) Signed-off-by: dependabot-preview[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 12d7336aa..e69d919e0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ # Load common requirements -r requirements-common.txt -numpy==1.18.1 +numpy==1.18.2 pandas==1.0.2 From de91e169bc6f3316f5d3a5ad52add147ec71eba6 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 23 Mar 2020 09:05:12 +0000 Subject: [PATCH 195/202] Bump tabulate from 0.8.6 to 0.8.7 Bumps [tabulate](https://github.com/astanin/python-tabulate) from 0.8.6 to 0.8.7. - [Release notes](https://github.com/astanin/python-tabulate/releases) - [Changelog](https://github.com/astanin/python-tabulate/blob/master/CHANGELOG) - [Commits](https://github.com/astanin/python-tabulate/compare/v0.8.6...v0.8.7) Signed-off-by: dependabot-preview[bot] --- requirements-common.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-common.txt b/requirements-common.txt index 7c2ad4283..c1e23dfa9 100644 --- a/requirements-common.txt +++ b/requirements-common.txt @@ -10,7 +10,7 @@ urllib3==1.25.8 wrapt==1.12.1 jsonschema==3.2.0 TA-Lib==0.4.17 -tabulate==0.8.6 +tabulate==0.8.7 pycoingecko==1.2.0 jinja2==2.11.1 From 98dc4b4ca3893452e80db2f58dd9d72baaeb5f26 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 23 Mar 2020 09:06:41 +0000 Subject: [PATCH 196/202] Bump ccxt from 1.24.31 to 1.24.83 Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.24.31 to 1.24.83. - [Release notes](https://github.com/ccxt/ccxt/releases) - [Changelog](https://github.com/ccxt/ccxt/blob/master/doc/exchanges-by-country.rst) - [Commits](https://github.com/ccxt/ccxt/compare/1.24.31...1.24.83) Signed-off-by: dependabot-preview[bot] --- requirements-common.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-common.txt b/requirements-common.txt index 7c2ad4283..6274297c2 100644 --- a/requirements-common.txt +++ b/requirements-common.txt @@ -1,6 +1,6 @@ # requirements without requirements installable via conda # mainly used for Raspberry pi installs -ccxt==1.24.31 +ccxt==1.24.83 SQLAlchemy==1.3.15 python-telegram-bot==12.4.2 arrow==0.15.5 From cb1bc5d5abb1b125be1282c7538f9e169d17d3b8 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 23 Mar 2020 09:15:43 +0000 Subject: [PATCH 197/202] Bump pandas from 1.0.2 to 1.0.3 Bumps [pandas](https://github.com/pandas-dev/pandas) from 1.0.2 to 1.0.3. - [Release notes](https://github.com/pandas-dev/pandas/releases) - [Changelog](https://github.com/pandas-dev/pandas/blob/master/RELEASE.md) - [Commits](https://github.com/pandas-dev/pandas/compare/v1.0.2...v1.0.3) Signed-off-by: dependabot-preview[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e69d919e0..b1a4b4403 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,4 @@ -r requirements-common.txt numpy==1.18.2 -pandas==1.0.2 +pandas==1.0.3 From be41981ef08f65d2d377d7a225456fbdc6221b83 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 24 Mar 2020 20:10:15 +0100 Subject: [PATCH 198/202] Test warnings with filter always on --- tests/test_configuration.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 1e9d6440d..79387ba7a 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -73,6 +73,7 @@ def test__args_to_config(caplog): configuration = Configuration(args) config = {} with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") # No warnings ... configuration._args_to_config(config, argname="strategy_path", logstring="DeadBeef") assert len(w) == 0 @@ -82,6 +83,7 @@ def test__args_to_config(caplog): configuration = Configuration(args) config = {} with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") # Deprecation warnings! configuration._args_to_config(config, argname="strategy_path", logstring="DeadBeef", deprecated_msg="Going away soon!") From d9a5e1cd48955041a6e41e1e6138be75b15c46f1 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 25 Mar 2020 08:30:18 +0100 Subject: [PATCH 199/202] Update docs/exchanges.md Co-Authored-By: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> --- docs/exchanges.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/exchanges.md b/docs/exchanges.md index c600077ce..06db26f89 100644 --- a/docs/exchanges.md +++ b/docs/exchanges.md @@ -83,4 +83,4 @@ Whether your exchange returns incomplete candles or not can be checked using [th Due to the danger of repainting, Freqtrade does not allow you to use this incomplete candle. -However, usually, this requirement is based on the need for the latest price - which can be aquired using the [data provider](strategy-customization.md#possible-options-for-dataprovider) from within the strategy. +However, if it is based on the need for the latest price for your strategy - then this requirement can be acquired using the [data provider](strategy-customization.md#possible-options-for-dataprovider) from within the strategy. From dfac7448d1260b81653e600fda27dc57d41af0ab Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 25 Mar 2020 08:33:22 +0100 Subject: [PATCH 200/202] fix typo Co-Authored-By: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> --- docs/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index 78c643868..9ecb0a13e 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -40,7 +40,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi | Parameter | Description | |------------|-------------| -| `max_open_trades` | **Required.** Number of open trades your bot is allowed to have. Only one open trade per pair is possible, so the lenght of your whitelist another limitation which can apply. If -1 then it is ignored (i.e. potentially unlimited open trades). [More information below](#configuring-amount-per-trade).
**Datatype:** Positive integer or -1. +| `max_open_trades` | **Required.** Number of open trades your bot is allowed to have. Only one open trade per pair is possible, so the length of your whitelist another limitation which can apply. If -1 then it is ignored (i.e. potentially unlimited open trades). [More information below](#configuring-amount-per-trade).
**Datatype:** Positive integer or -1. | `stake_currency` | **Required.** Crypto-currency used for trading. [Strategy Override](#parameters-in-the-strategy).
**Datatype:** String | `stake_amount` | **Required.** Amount of crypto-currency your bot will use for each trade. Set it to `"unlimited"` to allow the bot to use all available balance. [More information below](#configuring-amount-per-trade). [Strategy Override](#parameters-in-the-strategy).
**Datatype:** Positive float or `"unlimited"`. | `tradable_balance_ratio` | Ratio of the total account balance the bot is allowed to trade. [More information below](#configuring-amount-per-trade).
*Defaults to `0.99` 99%).*
**Datatype:** Positive float between `0.1` and `1.0`. From 6f687c97ce1743e43dc8b4a1bcb166cf7be4a523 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 25 Mar 2020 09:24:37 +0100 Subject: [PATCH 201/202] Update docs/configuration.md Co-Authored-By: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> --- docs/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index 9ecb0a13e..d8a9653c3 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -40,7 +40,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi | Parameter | Description | |------------|-------------| -| `max_open_trades` | **Required.** Number of open trades your bot is allowed to have. Only one open trade per pair is possible, so the length of your whitelist another limitation which can apply. If -1 then it is ignored (i.e. potentially unlimited open trades). [More information below](#configuring-amount-per-trade).
**Datatype:** Positive integer or -1. +| `max_open_trades` | **Required.** Number of open trades your bot is allowed to have. Only one open trade per pair is possible, so the length of your pairlist is another limitation which can apply. If -1 then it is ignored (i.e. potentially unlimited open trades, limited by the pairlist). [More information below](#configuring-amount-per-trade).
**Datatype:** Positive integer or -1. | `stake_currency` | **Required.** Crypto-currency used for trading. [Strategy Override](#parameters-in-the-strategy).
**Datatype:** String | `stake_amount` | **Required.** Amount of crypto-currency your bot will use for each trade. Set it to `"unlimited"` to allow the bot to use all available balance. [More information below](#configuring-amount-per-trade). [Strategy Override](#parameters-in-the-strategy).
**Datatype:** Positive float or `"unlimited"`. | `tradable_balance_ratio` | Ratio of the total account balance the bot is allowed to trade. [More information below](#configuring-amount-per-trade).
*Defaults to `0.99` 99%).*
**Datatype:** Positive float between `0.1` and `1.0`. From da191e4ac8a1cf0f1170c38ff2d2736c45069a94 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 28 Mar 2020 17:45:44 +0100 Subject: [PATCH 202/202] Version bump 2020.3 --- freqtrade/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/__init__.py b/freqtrade/__init__.py index bb1321237..080fde242 100644 --- a/freqtrade/__init__.py +++ b/freqtrade/__init__.py @@ -1,5 +1,5 @@ """ Freqtrade bot """ -__version__ = '2020.02' +__version__ = '2020.3' if __version__ == 'develop':

$U6w?tKT@c&g@Lvze-5=` z>6qHlyrxdRHs2Gw5X3|M#+o%*Lavm5f_^pDEXfkh9DPKZ`IoYoA*i|Mc~=g!;OooF z(B06x6I%H}PeLol=*~m6o^hSncx)%Q`vH3SQ@jptz(HyWeAxEX-XB3PM?lI%9G+@U znv(fURP7L}+HBhve*JG>3-dK?YF|y!uR9W!!*H~8V32-ox%BH)!pjdRe-S3MQmM}> zeUa$YlD!Bj^~;`fn!L!-gx`PitA0mIKWB-H9G!Y$vQDkchBI`=qKML~6}jaQ*i$EC z(R4nHOjlvdX|Sh>)5!GHFy>u~?pTngKY*n$I0Kf#&keAp@M8LMeKhZc72ighNhEOC z3cOub+)-xlv;r~GNZz#{R}t$n!^)!n0do^%$% z*|oPOSqPELcLz5#L{CXl1WtG`QMSyF0PT~*ySK0;8gtk8D~pqm7G}ddIKqLxC9H$n zcyn)eSqI^|M7&@Z#FUx3Oct1I$252|vofF!bp!F*kJ6XCoW zal$5N##?Bgekjqbi`71DI{UP2(|GOP*g~{(tZJv9cT;Z7_)C!>Xjtv_iES@OS-cWk zv}0>CogG?l&~*P;F2>oOhf~k+gsJK_eWyNz|zPRSm3P!_rfZ*H_! z!H_i*zR`s+;4HzwW5o+K6zCN+1b1lf#uIv{YCMtHshpK^Gwmb8c^{$8W69OQu4Plp zNqM?76@fI`K_K0e!$cC`q^~#V5*IEuq88=@Sybqs4Lp)n(;sXP*`hj@iGEzxAFgRagmDD9>{{+JzPdXc&8Fk(o=9L1=aEX-->1`Kd&BX@crzzr zsv-%f=8>cTV?dn00&~4kSt7yK<$I*l=rHUTIu36BsoC*WxI8kUg$1N#^ zJ)_<^8HX@FCa-?Sw1-0omp}=CoC6wL9Jk-8Z(RN*;MFd?hO3rA@9w*2bWf;`ZKWBp z467j+Zw2De3|_hHh1j%WDt7smXvJ&OsESVhYCJhWPmZdS+|;yy&u&B!@iuw-Gt2g3 zKCbYHTb}B|nN6U)unBe{CT}TsKL6(MuedH~qwD+>zycHK%;sdcb#KrDaH70sk1d%f zAjr)axYBrI#6D`(3?QnDb}cYEto1l(8BCx%NkaYlyEAz}nG=cTt+^}7zhna55(W&1 zT3Ojaa|l0p<$J|ZT=d}rTL$=I#ZjJLE#<-Pd&jcQzY7L?d7zJeUG{po<|xd+>wc?- zcVRd>?UbdMmk=_$>s`(k&Y|m7?I4EBax0z z3tA;0u$a0d!-PY}X4B1>6pQ{jp#z*%?Y%!ioTIm^hXL6aZaGss@W>M zIpy|lm>R*iqG&GSjiAkmj$bYLMF$h-be%)u9O$zr=pDhcvNPhb0aItYMK(RAD|z3n zIb4f#Mw*ja+WX_o*`=0|6JlRnpeb`6@<`B44^NwIdUU~4Qq!WCa~Nba6mw43`)AJC zrRerJD@pASkx;beq_%#qNU?tJ@g^z^)*RDbTgX8We6-kI&b;}+BG8c$v^faEcTwkj z=PsS^cF63@=KJN(VbgWUpE1+z-%6w7z)hM3ZlbFbj*dT|X9#U$OsSLKI|YGXd)ef@ zg+J4>*ji?vEGvGq_|0&1N-K2I2_T&a?6Cmxv?ccybcn#INT4$;4T9PgX;`aHn&nt<6^UO%PM)W$ymKqJ{>adUnt7! zf=~M-wuLhctp2=LH^!Y!(|B+{wypY|ys@k*RCzV;rROUJA?3y3?(Ka}E!4cEj33AT zw%p$HU?gW3l_^B3q(;egK}zI04!2y09}qRYhfaax=Kv1SFEYgs% zPJya%Qioi>P^B^*Eeo~ZbZ(G0-JAev<%+0}!ApEX0}YCw=L!?KJGb*eT@8|oe~s>MtVFdKuq;908uo@s|v4*eMty&hUjYNVNI8FrS5nxfb$uSbv#X1_A3I` ztJqSVBYFOG8Cn$jg`4!D}S+TDCx@iPQ@?Q6~ClEDM2Bae%i$1pyRB1P04+NwJsEtNGN07rD;ij&W7&i zCl1|@3ypkb`%_c`>{E?tz- z+!8h8*Zd0(%VX%;yAO<2KPMf6OFyRxgDT4Hm&)xPd!KzY0rjq1U%`|vx3?gZ-#V&J z@Bkf)hl$S3uxdpC3B7$LW4fz{DHHjv!$h-x!lU%=5V8qA#|rbxxSZ?%gzCm}qSr}P zjWxnZ>$1EX!$fa*$|E=w$*V{jPlLy7@b{McRXX-WpN)yqT(&4jMhn#-ECFcIQAVKsFedcw+{lkgTScGUEB0@6F>R-zE0e zfcQ%2>4@DIZ?dAR)b|azVKPVnLv-@N8>H+BRCdT7AmP6Tm#mPzjqeOazZg$E-O5jx zNE1bmoAzco#<#o~mxmWk3ENxa`8QBSOyrDzT1n4y4or)Qb{Wr<7%m#|k!!|!LN=Z0 zkP+IRxu|~oqG?u5TPT|OX)NSB==As^j5p8}>EHdVUok})@{7P!Ia2hy>P^^8U!lWb zxrd3D0tXgVglp({T<%LM$x`&&R$ymzhButCONuRZ`Gb?n=YvA^CMuZUR2TEszNk}l zHe8WNp~)R^{jxk7|6Yu@OpD{E^t4DcJ24;fsRu@^*7IQnWX?S$K19mKa=4og*N;tH z=GG-mkkVUc^C|UVbxOtlAGVK!eb?>@*nK(f1GuFPGnIPt2f$qpu zu?~%yf~V@#N7rA{547lGomqK8=Ev{V{Ftuihn=Y=)BPxh4w~$Qw=eoo{a8-Qi5~kS zni1T!JUe99p=F_r=2%9EBuyDk%YvO{Mx?X2qb%6H{QL%{3dgd|>keGE&UqAvQ8|ga zXiaX0qR679XUqK)`$z{Im3Z zE+P~m+xCV0+x(T_Y5Zy>xOc;1I$=?ZPA;(;drCHk?Of@rMu-ruH3S)W4(Sx zt=D3+aM+EUftLe#8EJ+!%|N#CcGma$iF-VlhZ7|b>? zfCLxDx@G*54dt|I1==EkZJ~@##FP2I?vrU*^=iDGRbzZ+a_DJ3h9$p>_BSma#ax1! zFsvIzyH%3+orrHYkx!*eaa*-2cRcmv+)nxrac{l#jrV<52@g%~ejqDC8Lh^uXFWXR zVS5*cgJ!pJ2tiv}MyIw1hJaR9_TH+4Cd&q&8h6u&Xn1n-qC)MnVPN3d`ozu*b0vQeo45DgY9ShLACG<1amDjlY%}|0!4F@qlzdYU8=7r)e0o#kECm$A{vNwMWJK8gQ4tCrSYDvn66bMDx?NRR0v^5U7WQ0amEDI`eOck;KVeC)TQZ-{ zT8J1gvs(~c7HG0J)xDd!tc=?8Ubv-r{miWhm=Xr|21BF9HJLd|fc zW6exfVn%5t=ANs}l>=(NJ~Lj;*HW#R1veTub(z7=ka1J9X}9;B=sOw50IV;P^?^Sn z;tOzm5k~@N`mnS9;R`Cac?g`APZus>)a`L-lPkR9VyHQU)AmAk?#-fB!6yMnQc+ob zTxT!88@PBn#XN97dm3Id)_!x8C|rzwY7kI8t8KSwk3%`*?W|fq4DMN;Ww+3tQ2%D_ zm{72BxsPP#S*&^up+_t$-rN>iTf$OYcOv879D6}wslBLh8cm{-*i-b44mum?hlst4 zglDF`sb}{|EPVsC@C}p&-&=mA_)SW@qD*NtSt2U-wNaF7pea7-TgaK5las<>Po{4((oR@qcQ?&CTHK^@3zIytTS3c2IW zaLu+52m2zCIs~M|3U(RMLtMQk2q`8OXNz_%`9a{kR z2zK2&pSI_4G_#BPwmi^;ex=0;=Y39%st&o{$XThIU?t`kC%=%6 zpZ`U|Rb~{u7HFlv+u|Uhh}f^6%kig9yraN1{`6r%!GI#0ol?Mc#yVf}H;w;6Ue-&} zkN;+Q`Pq#BL8-$Kw0$MZ#v^Y;>_c34l(}vg^BTh zF|dLDw(35`6?uV z(?v&|7mh3*TQtbPWG_y094n|J8+sxpGpNtbb$i zQC7S2c#=uAoWk4!aUKgo?nIFb0UYYmAQ)Ba5jSJhBXb<1=@&vQib5PY%gM%+iAarFrZ3)vIQD3U!xH7!sj9YE;d}2 z4p$cbtA>kJ^q@x>B|kn~Owz*V@}{vWe|Qp~%W(SIW5wfcs15f`@b%@vGx52onkXn) zQoS{!OPT!wjG%0Th|W0$yDMiqpZ#*go;vWgv24+tr{Og-*+lv51^J#bp>i?nVuGzH z6&@GTlac5lx&V%g7BkS|;%nh_MI3uwvx%=o?41bS3+$%DG}ixUc6+(q774Tgg*m0} zuA%TWo#!+W6%qiQEuIEb9ASGfoFe$>rE`{r${l0p;;-oS}>T9o3mffH+71;Y|Q z5E|y^Y~yn8C7gw49P_%Kk1QXH;lA&6LF%z8@!>SyI)kD0PLBY1c^OcFzKNWk18PDxpf6-}t@ak3J#i|Gmn zg#QT_2tTo!yY{cY3;u|UZ~GwmBfjt@@JB3R{s=_M20M)#JMER0e7vc-C3QcC#Y-_t zu+KhC{z(R5RGU@8DG|`gLOzlsJNiLSBH0F;u;OFXj(%*6Y~TpLb7gu?iBF%NQ$n-KI&8D9dMB|;Jk9f5B?{?O+DIcmdCf=Xk4>K-#G#ll z18Xd%m-v?@DfANT0{Xp?HbDqyF>_ca#4a@8$__$;Q^c^he@Y35+vRmKorP2ACbl@) z7zQN=lyJDx)8+?3M3;p2d(!ev$n_Mu36A*sfbxU5Li7glO`Ik~FEu|%QjA?1zKJu% z*rnzN!To+IFD>7MTu;F`>R7w8%MbF9P7_$pX#yijX#&rjA4HO*`_l7Gc+&7q`1;Qx zKgc&U;x;KS$U}<~lIY|GIgKPDFUbEDDLi}DlccO5`-S!7;5n1R*AuasJ|$M^vVuS- za3uxN@;78@=THV!eCA!rc|mw!&qfB*=LONRnQ8Na3>BMsHhDoFO96iOYuXd!1u^YM zLoAQkTbcG`C)1v+chR0C8j(VKl5~C=DldpnM04bj+$X-Y+$SUdy1XD&e1@tVloy0$ z^Js5R(kV&=Z)AGz6JOFPYUE&fa^(f_$s##&$h;uFwCAXiL*@mk@`;F!^t(zF5gp~+ z;FOq<(UD0qD*PvJ%W0}AiT_0S2OkPKg1SZ4oWy#Q!ha$OYqYdZcP6-BSa8}-n7nD1n|;@}u3RC1D6LQ;j&rD>qK_^@OH+04je z;RW1l-2a3+fUS{?7djir8|pRB@@sz0UA{&#U(|jL*+8E3XucDZ2`)fWCwwQ9oulbQ zeNAmWoHf%Fu(>eP_1-GfCVLWjPZ@gsX`KsXs>66C+$*A2ZPViBcJu&uSR4qps7@4@SJtYaqMygE6nP19bMf{#o4k?XYpuJoGmSqOE%8YLvIILjdkrGL;?>vAmK#5$1@avOQ$}N z-|gh@n4g-z1BbDc^XF}R{1krhqeJt9S1W$-Uus6fe;egyU~%z-*Nsxg(4{J5^Pn~# ze`%B)5BaQ%A1X#A*^|Qv-7JXyZT#S>CMo>j)hYboI%d=12j>hRVuPmh=l`kH6yXVe zaEaPnBSD*d>Xd^dvYCPz9Q1`9VqxZ;=PIv@zHm{>d2+jG@ruKD@OjdL_Sc%8zA){1 za$oHFH2j5E{`LHY8OrIn>8urbCMkurc_Zr4&qU$pB4UnxVjIs{#GJJ1oJGvNp!N7o zDf#^xJkHPxJ={)>_f5-P=;-m=-5e`Ew`z2@`TfvmS~c1zv2rfr!iQ2c_`_O*7vjQ4 zIh&s*E`$b8)t01wTTe?|2)>`e`uo0EBpr9*Reu9_p&q-Iv!*U=)-jxr$n6rtd2XB_ zJ0*(qTtyWRD(?L`L3V1ry;=13JCgMF?<5YGBA8obl^K$^(5<&`*9=b6^?dg0`JB+( zr>D;6)Y>_5oTRot?vNHfD*5iS55Of~^ll!h>A-#E&?m5gKQ4AeQqwhM;j59ru~77j zUW@hir+ME8O9vbN$b%lxP#z{y5n7ly_ zv{9J63ni$s<@YM`Z-*J&Vbouz^!P_bk3R@?6)tCb{Z8Y--|*{y=~m;7RlG9=*Vopv z{N=F5+RLL9+@S(#g@dTKT7^_{tYh{>j%`!InlqS`hx)ts@;Cx2cM3P2Lu5FXe|@z# zm$iD*;h8XSPP;CNQ(!B#d5lrCg&%KMTC=F|S>QQhcOZ0Tt-$Mtt2eTPWlcBl#+o_s z(aoyg*C+aYhB!PD?19^{YV=+RE`$^D+Gh0S&!jKAsV`rqYIagzzOuBec81f3i%QXd zV_f|g`=oO=!7rdd2W%b)fy^}R+U#x@NKP1;ui7OYD)|77)t6D9eYyFL-cMlEX@JJ5 z0h*CuPcoT4>GMKy$RnluCY9sR8LJtNcgLYPoWWoRGnGSWCoO{s?Vbq^H)?HJ_2)$0 zpW}3Y#+IOi+*QpyfbT38NU}NGk$+@l!LeUYqPi~yfdmMiE5`% ze(%xxv6H=h-HmA}zvZlA2I@sK{^2}t3KRI>!u@^XFL?>&d5C=N>6GWqXHuR~ssEq< z`+o`lcS48$Tll} zxv~Fd8+a;Tx7ff*nI4M&+n1t6k322^_c^eD@70?0;dHsN6$5yc_kA&db7m-$De4C? zfJ@e>Bm!_VJwyE<25?82h7n@lTdYmPCH|+kpiaN42*62$PB4HwwBLavq3C^gXtlbO z)#{Mq`*!qc;rrgo8f`l(cD3K^*Vf@=zHip1>C&`pRKW`axXEZBX8hhNiq&_N=KPC! zUd{Loxa(YmRDQD8c#t`L>wm?4USH+al;2&wKl8Xu;F_+p&Q1B9P-)UB$xP`zD?R1+ z)td5qN|FIwy+q~0=Hl6>BNlK?392mM3BvE4s^+PL-!OgW2sb8UaDO_VwY$n4it@XA z@i(~>^gF;?N?Pw2ROoVT^EQQ16fdsa?BuU51@d=*p-kWr6_)bzjG~REXv3`KZ;G`t z_zLyJqg!y(FS&xha1nni!jiKi{vJXrP9pw38?89+%v!OV_`Axb7dyn?shF7}J#_da(`iy14>Ez6?|&yN$IU8>W!OIm41DH%YG*%1s^mc7#>p z{l=mpvFP7(#478-E%7#ALD~PiR;pl9Cc}?r}(Y)>9y2fFa{g-f{KVt7i z_Ee-!wc@QeMKjyVqEmib9?koen@^GiWo93pR=b22v?-RWLSU;g^^gj5J!u@dnfv+9 zfkAf;q~00YFX6fFJN*(sn|h=F#%Q*b>x-J1;kuU0LB;#rPjQrnY2=y(2rjkTB^~P3 z94@{H8{W?Qt_#<+Mrsg1jNst&ILtaxx z$ndE^R5g4KO{feeKCtBB+us*{@b{N}fcU|z51YXMBl%mrvBlxnR3!PB#A!+Lals+U z@79q2jl<>W$!kEv(+T?7pAA=M(H{*LFeg?S?iD_mJ}_Jkiavb~y+`5xC&)Q;K5+jt zlX3qzTmtvcC_#VHIkdo=oOw#~Z9JJ$cbt~{c(>nxgD7wR;+=VdpSUKgRLC_YG-G0@ zaG`<*&O#Q~HI3q$3D=wq+c7?Nmh!QaPnI8h;k!WTNCYBX!;>aFgH{-mp z{f1?~YXy3&zyKXAIVRw+)gYykDi1bVux;)^eDee|dYd=F=RC!TMDsYO;rNee(EI^o zMl%xzGV+05ADQR_r%2>#pT2UX(#`**(on{sjt(rXsJ+d!*Qn%=iaviA_gfkCs%)h# z;cz%b)8~(55>?Hf-|8F-B{pCTBSsW|{%%#cR+l$&cWy$5|E!%42_)5A>Zq-Pi*Vhk z-v5zQZ#?%1tIyxY8YebAjvfg8(4!iK4Br!&JC6s00Th|6N#oB1s_pai-^-Gk1`}W$ zqf9bbo#Z^1g+&_X0M!$H%jbB%DARJYqdUt}?$k7y4l08_snj$@$*2r;5h4h59?F*A zt0;n4@M(728|$7_Q3M5S|Mp>Jb%+kO_dM=$bfCN1F}axm9$4blVYpon%GhkcfDb(oyQwi8<6yvz_dqN_SD{z zu?URA#>x12I}QsjpX4V>Qb}X>vA}9LRsw(QRF+)7S6OmB+IHKQu-!hwL(41G9S2Eq zeyqIatq=-(Lhd<6O3;GB61pK5S!ImeI|?oOyNGvP#b-xDGcN+Z^Kfv35zSzcStk(Y z8wbMnK3e0N02qbe`2|K0?gC=eet}7tJY}>%e?p0Q6)a2sN*#U1-d@@G_VqG^77O!(y&2 zR*It1v6GGx^C9eA6-P{_)J61{>KniWK*Mx}P*$O7t4N;dMXm2$CCPII`xlN>}H_K^y>bB9dCQ@x7q~a(SuqhQWT+6>cbEfQoaH$%=FQzWNLm{@pe+-nDm2nV!BQ`dy%D> zBg=|A=$ywqAcFL4!BY!)6+9A--pcWC({??}p>bvOW4HMy>NaprEP+1lU0o0N^~9Ew^f}SuLk5L9odZBS%nqY zQU!n6kReU^5&K|gcc3-~=_G*Pm&B8AIS>1|kv?4;SxXfuqh+9F` zowJQCWz>QQT2Rp*^MT!rs=i4|!y{B&cMLN#-?u-H7oY!Hp_G)u-6jVIRyGM@6=by4 z%|e;HV;I+!ZkyA_6*V6GDO(T!Mf(10RN?}((x3m-J{i_$_T@2uC6!Fw4y)yxBns@y z?!nmh7^~rbLTO#bgWX&ju)j3rz5zMO6r6FGs!8<$2Sn97G}-!l|A5|yVnIK@LDgJ9 z-LV>NL##fCRH_zm`(v^5Y3Y{1i_vwXevTvoy67UQVty7^v8A^HrbyPB!7B3>idgif zCw3iIpq*~QU|%kkqIbN*e%-fVLhdsqwfEdM^^lUBqp=HV0gpk?t?uVyR_>GTCh1n9 z(*n_9gzPaYg}o4QS@x@ekUv2^f67?>MJS3S>jzj==}oUK4cA^Lg0U@j9Xi5L_2H3M zsR(%?%6!Wd%kZ4`glKpKyGx@P8wndF3kV*`fLcdSO?peqf#i{NiNQXPstk?foPnjw zYL`+)J{w~EwLj~(dpJzOk9U+Al}J-xb)5cGGiSnE#_AvNGC2^holV7M@4jbFZ-W}k zF(HWRwbFoJqc#_!#6?shsAbd@UhZR$WB;UKEmQSgg*&gOJ7f2oD60fI=qrn_pm!`Q ztO(m1o`tQ}GOlG_kjz#HUuKJG%-9w)cxYbbx6D3Z4UvXB z3M-~JWIsS9hV0*plk|po$o`}3x7nK__QzT#kM%IZONb64isChFH`5wPhmVp-bi6+j z%`Or<2CbXXDMA7Z?zC0B9$V4zCD^}M70MW}qLci^+a(^pto~T-C-CTe-d9JnzKo5S zIj$XWLhlnEVU^yjx;NRaG!?zZr@`#eYJW~RmJlU7DvfJt7s@%0x&9nX{&-*PYLqou zs-!e(M)+j=Udd0caKKcY_{ zqP(&ZoN-}AqTiyF30^UNm#W%WUIA0j^olVY8nqDTJ|$N76<%cvU-)Nz1i9em z(0s)|$xN``@&V>M!dDCj!H|5#U(lp;Vc5`?rvf_O2kYvUGr4B7wzwFL)$ck^L&zh|Govu75-<2tR#c;!2;^m$}Y{krfbhdoO?aWu)&V0pt z6MV%x6kqXXeyipy-bw#$NyAqR;Ys2vW@)hF%r0RD_*oyhcg6$t{#Z-EsErbS(N}Ol-6E)E?Rpnwf0lGwP>nhCr-~y96L=*(lHZ% z)+PHS;gbs4jO1rC(gfxtKH_i^`%k9icVtdtNza(fUmCLa$;#+Z3>USOA>Y9=uUZqo z!3XbU$~APGSMz=1=elKuGd;#ClQ#xB7g;d=6!W)bg$sG%{1|=fxu3lsok;wMW$Yu$ z??Z)Zn+|CljzI@?;52_Ym?DLNY43w`s8l9t{;1+;F2nZTaFVrPUbd*6pJxuaOB#f)Zg}tt;9c$Gq!Ub{*^HYk8@g- z@1bhXD&WD^SOXX3IJ**_E|)*=bL`5(XpP{}5(n0=+3CC8SoAkhr5fmny#|q{h{D?m7SB_kcynEg#ImVgy(5dVS#D)n}MS=9+8@jOn*Gg zn?irAID=^b{o&|se#p=SQh$X(IB>+OX|>=lj!t71;c(!Tl@YTt-Yt)2HmY(Ge958c zcxH1pGu{S$@LdjnF!Rck_K}{()X9+x_~eBISGm_SH{J#K4i6-p5s^=`c4_&9b1&DG z0Q6Vr{|OjbKe&^x3fHulBmmJoBt{}#zlF(^Lm5~^GLajU%N72$++ji-+_C(S%I!%V z(rl06`G-R4I0o4PZF>2M9qzxRaV>> z8%}2@-#z)J-D%n#J)fIO>p{Dj(L5BBu>5b3efT!TEF7{Ak*WhDD_w|`GpBcmQ>8fi z6qD5o>B`H-1Hs{trdN%%pBgTTkB1i#Nk|+~{!g|Pk77DC5?AkVG3~O_$^6AwZem$X z5DX2}AqdX$BohR;WeGv>OtyrRCZKy_jQI>YiGNF?3jS^uVw`nX+Pl%J*vPsQK4Z;N z>uDzKVzP>HQ9qus$%mS*i!Tj-pJSKRa%vFyRhS*$P7JScmXl4dl^$45a zBcyj$MC@k3M)tSodCCLLR?UX;ninl9cAf<-%I6VFYO~84B259lSbMHy}$+md+7j@HD9~S zeWf!LHDOaG75G5NxVG7SZBO)OEI*~B^KCVaYde!ljD9`CxVFEpDH9%A=0IDmJF`5v z={}Ae%G@}p9!gOcAD1MB{+f3>z#9ox4^!;^Ki+u-@(;J z7~n!iHFKh776wlm(YKX)e{YX{(%pYM2lwB%rT@a5)-I*{RF7?=SGJ+11K8u79@}kP zyW4%GIniS;svdK$wIq6Mo9eMdiP3LTkDW;Bu|!X88uWhZsW$1Ufux>_f#$63?mW|u z)2$)c{?K>aH7wz#oa`6ENY;c&6~P(BpB`MK)k5rov91^#;dbKS&3ss)!iX8YIgSH3mJgV-l9EtFF!yiTILRb$ChG?sbQk81C?r>Nyf! z@<$zA@|#ft?ctn)4Z`roh@NkcQV0EpQF20@pdFM;Qlm@0m>gY#VCc3U;F5zLC%A-}zwFDd7@sz{BoReo2I^rHFatkKCHy+un38{MamAD@ zOF{qa=E_C$yhFs42*5jiOv(R-c#@Yru6PneKU9q$HW+qpI*}0x+_^-R3>5+LcOFsl z4=E8P7~j7=qJ*8Q<2~skN_=S|O48u<|N4lMq`>TR7*Udl${r%3WGpZo;GW za5^!+Qu_E3=2wEv7oL%;#Si$RiY__8)`|PqdZql|NGvg;OODadzk=5sH6HvuzvgM~ z{(9xh{)$o5-TUtz9bCfUwGg=+;aAeZCCY`An;U#5;zg|aZXJdv{BA?PrD!63F;%{&#&>P-b zdTZ_H>Khm3pfZ#CmEUYHgYFdTPbI_!R=>SaVi%VPbdH_4%yEk_l#O%?4MD~8u?{AB z(wtHUaM!OG$oG^htVD4son2Uo>L5+Hi=d6F@ne(2N}Lf%iYiGAOPa6}lT9sLR@$%< zcewT0hm|;$o#?7;PI6d@yRzeksBB7DiK!Tks>bIGuI$-`m3-@i2rCgSE`2x&R6Ta- zyy%3J>`VzKsh*Q!u#tFcc)Xa));^v#-Wu1;xhmdzhSrEbJxUBR&nSb;lUgIbV3ZCz z8I@q?XWPfgky2I8HHACg7@34~e}TUC_`A|*gKUf9I>~FD!jWqy(&D2HDsq|4RNvk z4Pp&B4ci~{Opxeqr; zetrvRY28FZx^TX(G&#`p?3 zV-=&DmEgy;5=^zbM}*K(g13nfsu+I=p#}8*8HDgg=REeo$l<=fRu1>L<#4ZPyE^Vc z*P8z2jb{{5v>J4$&8TBibwz_(7HU^C!Rmm83uvlBc4T>@BYHs92 z8ZXi@3gsupEFgc=ddVnN1(dEt{&s-}XE$pP-9pj%$3!-UM%EwU2&(*^z(qB{4(sX* z>|tEJOB%9hoKLsJ^uZ#_4nd>-cOFgkBY08-YjevQy{?wsP#dO}J?CIZH^oe9Q8v%Y ztLdE^=_ttBF#jOxFdFlU02txyuGoyGY~!XNe^EO%1qBXj%DQ&e<&gWeVhdI3=wieZ zS5mVo(4+%*ClWs}-RC2Tnq3SHm!pLX0S;$I$`7L}=)YHDOR35ncQO8Ij^^N@U;@jk zLKRCajFcpw%_y7EP5XF=s{f=^E!^;p#^8(Lh8fR!< zy+(=}As8d{MB=DBsxgg}Qj$ zv^!`JdTztVjC4tcOCdhN zo(!6@2s*VN;b}8eBfg@9!fE|TPZAQMT=%R6e!Gb3S|mCz)?#SY?_c!sh~3tp#cNZ8 zywkL|1HH458`AqRkA%`M)ZiOxoHM=L33(VA@wwYn;2b88VpF44&J0Q-D!AL6>}qxY zp64;jNx3aQ8nRD>?3buQ`Oz6(lYO{fZB$9c`(n_zr~cu|JkS527Z}W0e~fOu7Pk(# zZXMuTulWzU^^fRP>Ld(x&$m{_`#Y!I4Q1Bof#SfMRUt4|=It+*T;{5=n ztYSS3fS%s&Opn7&%=ur|Yvx~A1~{h-DQKaLo^Z{ZR?{mPmc1pO|I2sIr(PStdU$dA zlB3sU^v+|v!xo*3E}p;S9W0}Lck9}vnXNa^tnA3hcH?d(6eceRKtlM<2cJ^3%L- zk$&Bm@w(RD0KMsI;d?gfj}GH6tXI~*j2wIm3QL0$`f1d6gApY7rcu9f7>c&LX`5V! z$yj&NXWah?eTbIDY3~Y0Gq;l7z0C>^+%vK~*lN@*fz)hS`O*lQRrijFchq$Z#G7bO zvReX8RGA3vcY$qI^g91t4;y}~54a{|4E2Y+B(hcQFp?X&Rv(jWRCTh;$;kE7t$w*2 z@=l=Z_+bM7kZVn#gG6b0uxZ&ZgFEjnwrZN0OqDas*&71QcBgASbwcxUy}}&+I`|@L z6cjq}9yTy`kjC4}YuYS(D;(6oc=miccD|ks(Ocu#j3wl@1KVr~tpZWgTik)Eq{k6c z%GsgzXfcBKA2=3IB2M(BSq)o3k~A=(y+=54fsofx9sM%|BCv@^HzFOlFbqfXp%9Vw$m z`(?!59%w>01hz$L_LK*9hiT+=e*%nQ(%@lhf)}(8A&N3e9@EH;4C`Y$0vX3rKmC+9 z3?Yd!aQ@N>d=p=%PqT*cK!$_+m;GB=u<72b!!?cJz&l~QGrNuIVg?#%Sdq;$93${W0T# zywTKw?-*;dM=M;#@lgs-(KiZq+D2?KGczG9xn+H2#lYET1^+dV{bBx0hP%a|lW=ChZJE~$hf^MstZ$^7`mZx8vRmn=itP*% z@@KODmrmc6SvSy|aXpWJ>f|C_x8P}`vRQu1#&1=d@SAB=$d6T=XQ6{`LQHGb^?e8y zlmc{FRUP_m&3p^I)=g5Lb4zk5qt0B2?~H|9?>}=X1!pd0!kJ4cN-8By8Ix5RRUJ}L z9E1M(g-$6_FzzflbA?LJTp{bsrOZ5YDHW-uuuP>XYhI#xBxCrkayws`&o+N+Wi!@L z#PW7ob-iAr{(C3mafHGcxug;!O4!K#4VKg5?U;g-%Zm5o-=|o;T4EF(!MA5Z>cff< zlWyn!hL4sUPM2GYkic3LSo;9}wBygixT%phiQL+GTv!GF<(oV4=P}%}N8Pf7FUpOG zc(2`qKTq)Y`TVzp|4!z=MYwnnf1ad;sgSWAMsEEI>)_)Y z3scb*LXOxz25sg-5{!-kHp=Fz2<+fujG{CpAea{~{IUzdT zYs@Bb&yAeqnS>$sm#wg(H+gX~w(8=;=;Ey_j50XHBZW&WI!sZ2a+p26YY7)q!evyv z1S{b|Hl$W=<1$NF^r*y#v6y}GaV7%0lfG1yKY)B(wBkR&CCoHtXMOgE3?wZcG5yS-PF%~^bQ!!Uy!y}5 zkElm^qw2>-E=SzH<8r-ntT6Q^=1Y4CK`%I z=soT7p0(25kMMW)yi<=d)Ql(UGW?~UtZ;gAp3{>>Nj-V6Uz4KTiJCJMN&1azg7b zt8ZLB25t*&r|3lLGQ)r8fN)S<)PNdMRlmvq=H5EMG)p9deN1+dd#9)-EVLDKpzM{= zpX!UgPWM~ETHmtsOo333m&F`7YOKKDdK9`DmMi~7C+S`@T87UbYkCxmHk8Ty9Qrxl zV$-r^fvf5G6(QqBQnAbYw5a22#JKTzkaTTh-Lex1?!LExW2#qVsF>=9GZd%;i#o1g z_2(dK=yM!{s&%{~QT;f~x-&zCSwmSYYq+F$u>Y?Xa$I@u^sx^}Yn+jIkiU_DT4;7&hWjgg z`j#rM(YGXGcUyr2 zeA2>pRUYW(qt+Ar;M;t{Vxy$}1}URu(YsvyeP*AI`V39%GmRPR&ER^Y{+Gi<-@17~ ze6aMfYT^KBOuBIoyOix=Ook!}CaIQ0qw3cx+) zkIleRN?N{7x>A+H+hmvKz&2Kk!dgzy0FaP)mjHF5!!G?ctHv5QD&4l5yUjt4;`&u835^;WCfLgH z(a@X#>Q{b~B$$yLpSaDF2B!I2G%&gRhS99IOwjL;F~KCxihheBFn8qm+}#EKgOG9g zfyGl1?A^ckll0r$i;L**i;F)ZEMR@O(!4ki_w8OhhAy34oN%twhBv(-^LI)NsQ2Ky zs^k!fi31Gtu$-m|cAX?5HU)JHpso|4C7A=xW#M6S;wEwu`1I$g@bhsWYJ0G;ydUYXT3vuVSs1gss|jF8Zv_$ zLJKkIS;oUJUJ$z+FZ#GZL>2QKIOez&xf+(8lhlS>!2$pqI#FOBT<-N!F8ka4x`Rw` zwwXF55HR_QRc0c)>vPiA{arl_9M3V)$F;w~Zgvd>U&--l`WoSV!$jfb7nhlPCNfdo z*zabaNsNTR?fDHmV>9IqYAgiil%v^tcr0W?qB3Xbm#DGOH>bA8uQGH(%Aa3HduW`A zzMJTygFJ}0f;LofxCIv!78&cle*y+^#LwB-@L^(|$-vloj6qn)%FqgrQic>S%_AyI zsc5a{rIA%1$`o@e7Co)9?dUCEsQ?On3Ing;WJPh%DuxQKwn7aPiW}VuhPp16(Zugb zVP*8);frR_!pb-B&T_lA+=Zfb;+r{2x7t*JWjhT8qup$X>#R>{ESf4(+DZDyWHx&K zz)(nrZyRE#o~)Ixm4>uiI5gwNs)dH^RT5}M$_tJg*LZ1MmX4_%%~pf;YiU)w5Q|6L zSW{{+>)Pel{3uFm;+iWA!Q@s5Cif!-Z)R;hlQ$$qs=-nLT&@L0(@MhS&UE2&b;O`X zNjTi282HKVZn5k!-+d+DQx>f_rS&@~nM{Lk_a%ba1_J+$f&$NCVC#+y(@}Z$0UhdG zW*-a%eq5kUDXb60H#^bIj5ZqSRQ%XfhFlX9_N(u~Rom)qN{nifgG4xZ3xc54`L?$%IXf0^A) zy)9takt%$qD`q-@E$o)e^U9_qVAzow{^fPLZ+a57HM)<+sI!&G@FW z;9(tLIkoaoK19$AtY^900lEBM9^Z-|!!?`FD41q+{3@1A;A?XSOXhS5yqrE*GFcfJ z&^KtnYVLpoSWS`47)z$2I;~>T(@3t8On!N?WcIivv)3h=)197mBojTG+pp77CwJ{& z$$Z__GC`}SNTwoNRW~>-OTg7rBs1nJP>{kYlF0=+H#h|*pu%Y+GnSCdgh=w>{=XE- z*F_|)8WBmWCLXb0w1V#%(Qk-Iw!WB8|Fl?vHi4RNf=sS^4~H<0{Ou-vZ6FfZU9OSl zJ5}T_B$7o_kmiqa*za_;>`B<~UY&*+7-^A7PjVV&#!wHa>kfuGA(EbCk$jt@epS}r zDvyH^ud-zHeNNObi)5c$B#-uje&U<}-}}!cYQ6v5q1H9b{B1YPoJ0{O1{u>EIGl2K z%F1Hd-RW{Lce!&gdjvL~J_oZWsl$9Jk|_A{e$D%z^|=@FNw_ARvT8^%v+7z-MeMb# z$(0qqsi{Y(Rv~)@FX(|?AzN4r!_hp3mP3mwKE?r+)Z~2lhIP{ z7GMm|LtiOQ{cq`%xl=Agorb|r23li<7JrFOe}|0K4-aD!x0OzmCzt;`mk%Vly)sV> z5X)JtFGP=i9*2azy?@g7CK;Ad+~eoPR=}7<%NNFl5zS2XeV!T@byvST8uKPTJnweG zCOgF(HAP2bUaq1sWf_#?NY#11n0gU7)>t+#^ywS4a9)^Lc+ZoCcMJ(9zvu;e(Vn_z z^BhFFPG(?KexJivRf$Fwr{&p;nG9kT2~Czu0Ryb8rOr`frgLHcHR=>-C+E5Idm(yEtU^S-ejwFK5#I7^@ThKc~xK-d`AGTs^eus>092=CKIPa?7eyXdXyhFO+BG2t7(YtCKd+?ISb@e*|Oe zq0fHCjjgv==Xn!h0Eem8AJy{+uegL~6aBeB)wrv7rAN4F$0vl{pJynM=gD(HY#wTT zYpUR9U*eW~Jrh{`{h5Yq63~MeC>b-PL`TA;+Pm%5@3DDPj1Vz&wp5#S9 z9LvxKb~LN5jsDx@_^VN}Y@J5Dv7~B5~a4${ftZd<6*L_p0nzl$_V?EBX7Gt2>Tq$aYvA;gA5Kd7ew#IAM|1Xe4C!0H4=U@4&Iw~_=-;6U%!0VsEvkLuP> zFz1+6i1U8ac;LEWu=e!~Qx-69&}nhnP6#`2oyP5V6z zvfqkUcxg0gRAulZfyPL5%4F(GCU!OhP4I3mj5ou_*^pNV`KNBp32rf>5IcMsVQ-1% zjfmKr!lcYT^dc*CEp8Ee+#_6N(d!B#fo>OB*^SZc?>yWOfU{B5$-5nCq}YtRLQGv} z4H!jxLdLaC?rVFa*TW6xGl~wH#g(UzdN}V7AKZHQ_t3t_p8LSs z=kSc4M`y3y+wv9vdg(GIUsrKB1t4D^LpS`EZjfujBkX2g*LY$q=4dVAV>mXarauf? ziXJz|a4%wo(0OA)rb*h|+A&#fTDCWhRsEWFy;sq$V<+dWWlyG%eX?QM33GZTM=fWi zdO3S4Q=khT4PE#bMZw<4V{=i@)_guKE%(_sEHg9OV_DA*QwDH0a0k0WMx-kg+_ikF z4&B1bB!}wbO4E=U13%1pbL=nE!u*Y*uzh%Li_ZLH%-*llJHa&F;1~MN2A!6*lJcYVjrXmPT2gXh2=ktsF1DjzjsolIVBLH zI)t%2l+h)8pCKe=g)%9Cuqs-lH^({UNi)MEjH0(t*IxVr%p{_}i#S-laF}u#zQj#? zotyT8G_8x9wv(H72pOJr(>AI6KaJc@(0h*`rP|eQ6oHF``=7w1S%j==SdSlL;1WsY>EdS-B6q6=sE!W`cT>)9)yB*W2UvR)O~9p>jl zi$r0U@^NJ*M`6$7DC~uNP+8#-ujj++z=9QmLtlD-_O3pL{PbOo4*Ngvy3t{XuP(Pc zIS$iJ|NUcV=H15H?|T)DtHGP3>FK4>(e%E=x_T9eziC~(T{Jy2Nax1L-r&CFc}!TX z6}=MF?sh=pFPgJ3&jW8n6P9>?pd0&ELA*Iq({7)z`y(|i>`C898Vl(%VY{O|*ls-3 z%nM^<*u@I|Apg`Ej!yT716`#5wNt;6F8?~|^3Ajge#^C9S$!H7*%ZU!ej|?@sb^__ z_NBi$&Ck%oSRMaCIM{w4=fTU~nC7;46J1|^5W*5hnu|)LxXV3XgyAg`247s1E@px+ zP(10_i?R3mZ*Lir?3@0l$LLuwTH8&DgeMvFW$^Y&`WC#@oak5WF4$Rl%dn5iyq4Vs z`i8haNh|??i_~m~7ZjXFiEa3w)i!(|kcz=w#@fXh;>32cpWL3I$o_B1;CNl^j843+ zqTzOuoqRGw{KP)($-anw2=-fw&Dx0c(_X*)7CmQ*6Ja0oylMMYM#fn zp0otxJ_gaBXp^UxGdZ|}KI{VSOM~lh^ zNYgGPjsTbr#CAL7{Llm2H*P;JH@4x%1GsT-zfkkBS7WU?WNo zA~tFtxXbuzb2+MGOGgyPVFxX*+5i7B_a*RAmFM0wnFN9a&jb=1da=eD+re>(4Yr|! zH7CsI8Ju8L#H5DB8)~t7ZE6WnK4mckjE6zmYpdScO7CZvTU)hX?X9)7&7MG5!X6eQ z;?fD@5`tn_lKI~M^StNGnFSC@?)UwE8qS>MU7z=TpY{Kg=zU(`b@tpG%-TzvB{vaZ zZf9!Y1%BX9;Ft}axsh<8+bbKnozug?XB!)Ahm1fe3(<(UsL`SzA7JsQpfT`W7kfUk zu+OGc?CD}^yAW=Qx8CHJoAL6ycp1L&6>sC^w{iaDNpEiHQCGg1H*0Y?Fuy`=Zs7hA zf(h^t+l-e)cV}emn>q>9gj{Vegd}Go<&*uQE8q+kizT7Y{7mnhd#ymt=4c^6l1) z=kqeyIfwAx1=H^$-k2l?i^6~?MK7tF#eA;T_#C1>;*W|4s1^P|>0e!cLnY>wI zCYR`KjM24~xc0UvP#k%cal&*=Y$J+j%b)FU$p zv=;N{LBF3RxCrNUfq4WZ*Bb~<_>i6XH2n>jA@mRZ?!82jy_!1|pu~#qdSAi&NMlxT z3~9{(-m2AcHEQqyP@xG(0k@Z;?%%oQG&CfvrF zeldrClXt^0cYnoqA9#*VBf-JF4cOs>OXgJ1LXylib~7#V7{|OUBRBCj*s}wi^-`5k zj+sW%Sh^vTAvZ|i;Zc27aO6(Yehr`CF~6 zlt-Sj8>3Kf9R=mFKT;V4EF>Bz0q4F22x^obKL9`Hw4bLP0Ad9JdAfwX8Z6^L&JK6+ z?Y5q8N@s`j7+rAHJB>zz-Yf3TN;0}@@;H8jBM6w(#8`UC$)V0B2Xo1{tG`uSzd%8o z$n~3Z#d3D)y>OxsH_Irc;{FF`5=~P?fpSw@*pEstl^=;lA1BtC@HxQFQ~Y3C_|I6_ zrccHJeDe!>@N=|%{oE{x*Gn;J!I^Lf;5Y>)8!%{rEiLe-?JV zig98S61#cN({k=E4@jSFGKNL_1(ib97sx7pN%$eO?rl{xy8et6e)t4phAeq<4atFi9(7PPjP;}-Z z>J~c={3|;SoVi2P2ekYmvnNiY4`Yo!ToC6tkZ$w=%?;>%uaZp-miPwJWC8b@Hg2(B zMlJCj(h{%5SmGa&g7>-F<71P}z%GB#X0^vlmOa)kUu4+j`;Qs|(g#Q5?DBIYD3>@7 z(qW(}B21x9t@5}PqO52RsmCfs8J;#}&0Q&MaVeA5Ya}X6k?M^rQX~QxCC-~5Euo9a zmUCeW%OU6-2|ojNHucQc^gRW~0z&xk^ed0cwVjj~iyNXvsd;`! zBMd(2I0JLY@YzV3IrPM7F^_A(tktBQ3Qi(BdGvf_C92pj;5Au>yvSv#3TYUWOtqKJ zO9g(G2+x@JShg-VBuvmy!iKa zyQ8m922PgQsBQc=g@YHleI~}#PS75M34uh&I#24|{FN$>GZ!?0Fl z0gPkO{0RvTZQ(bKJK-EUxmWKS4I;TFEpQhi zN;ZvVvHmwK*1slxh8pL_M3c<5iuF&$i1q93v3ZZEKf3HswDSS|;+cQUCIej+adHSF zkr}9kOkYR>Ot0$xW2aPAxHx=-P=Zjq-s^M!krG+oNl~UYQfqHvxE?46KVY~X7W%g{ zFpty=UjD~#W=Vt|miC$Gq+HLisfpJ z?6EIrxkTTDbcRSdvsHrF`H_^U&HcJJiO2peqLPZ6B@UywBV~pL@61VyelRavlYOaB7^pK{-gx z@ix)l!vp~j`E(v4K%5g`5SvH&wkFuab5ux)w@2ef@POZxzK(YDqk~4rD(Gg}p*{nt zx!9Uo$4-+lLQI07-q~athTt8w8c?n6INic&ku5YHSjzUU*lEsp-M7o7FA z=;#w)0As-V=AXf%m~x6BJs6++3oMT10L++7p}ZO1<`xX+S5H&TtF$3oua zD#$Ed4(S)z>dn|gDf9FrBZ)7ZgOL7N1LD>RWz|PE8h;G!CK}pS7K|Sx`5=Wj`4sFO zFuLa7o<-GV;J!02MS`>gucul1kT8yJnMRR_$`0yh=I1eS83PC)>yZcJ2Od zG+cl@wX+;y9lb5!8dXU6#j;K0TrIY!Vr5PSx6xtjQayLkEbWpp@>(Co$U}_&I?vM^ zJ+K3S0a!z9ggt#Fom!Xrv`>GWJ;*myPb7CHD|u6&N#g)$_5;|YdVXn;7`Jk=}Q#q{p8|DbFlqt`xUGfY_nAWD>@$`0YFrW<9*wv1;s) zv6o5mfe$oT*TO!DJbxt_-(wdKO}&G`;AKyD5)=q#=u7y(1pFm>>7dS{a2NG-s+e;ZS7V+`PU_??D#|Ft6pXLbq_-kej+jZJ zpm{ckz!mdc>E>jvv02>ICyc}w^BkN%koq+*H9dSEIyN`D2vSFyrO$AYXT2z1eh6=M zlR_sL6iPn#URyK}g*3*5y+4CQA9KzqFkZuyi7N6qySMZ5=Woffi33&Niv+KwDr7yR zAc1Hh^2>&VY%rRx=Zt`OiJMNqL54Ve&=9A4r8rIO zt?Ol}M>TX4%pF6VCT>}04w}u4Pq83B*AS|gd}IjKRUbu#YJ&;qd{Q$dwsnbqBxyb= z!cd(p^GUsEzjTB z+u?$|DnjIDU~e`V(kZRFp9NS9n*#$#r}`GBQ{6yL8I(>n`_Ac>bgB#D(y4AI@!@fq z<|QVbD%oYoldm;-I@O|N`Be8Aft9K?pQ<&mQvG!KR1N8&<5QMp}Dvr)!YM$@Q19+yV-EqhEF)hA=psO}Q4 ziDn~>Dm+ThltvZ#q!Q)XeM2wLHl10XWdZM2lOpRSMFRA+tfA-4p_6ADhgP1o7Uz`m zX-sj>s8~D72uk!DZ^lcEM3arISDP1;L-pU8FrztCH}fuY+8nCImJDIrjSOMM#(H)p z21*ylujlyIF>N-IH~TYY0@Ww6Rw#k0;=E4NvTOvpdi~WJvZsC^&NXrmcYk1}PklGw zKIRR6+m4i}I0xx`6#JsZA1hv*`j{B*4{hZ6MiwdR$wVoxZwHqz*y|^aU|q^RIi4^I zD>}%IBd7^EMU+*S*ooh6dR6kQL(wU!e@A)jPgMPjEJ`ty zn6i>iCQoy9HPIiChcYJi&@2(=59ky(vnKY;hO~1+MyX~@B&c0UtQT7K2NO-s{2Ixb zQ~FCYvFb&1_uZaMlAclo_a$p4^s9EZ3Zd&|?Qp!>OrMJ(n7v7@`3IU!@K>#vO>(F* zAjwei>~poY7XCmA`IOa%g}|drY?~O+APyixPp9)lXPjLwM)A>_}=W2<>T2K*`(_d zV|HKTyq+&gXtD8fg3z0bcnn|)x+45dPPs}mWfT;jbJd(HFt=C4rd(x{50z!GvEWj7 ziEU8!zE37x-AD;nqk}rJ&ysMJ1~t4GqhxLBh0&%?6iw|vO;g7wZ|cO@gsW4f9id{v z#Q3J3HQj0>(yiVmb6SCi6a1>=g=diADL{rN!FfgT{7LO?V{JF5NoJmoJxbZC#H<~0 zy&ss!n{QiEsu~QT#lIFP_#E?-1B*iy>FZ^fote8TN1cO6s5u;HcexR0Hz7qT=u^%T zXm`la*YCB*$Jl)#X^fp&VX`Q@1=c7#rzOhnBLvu$1Si!S;dEvsUDW=A34a3tb*d!x z5eTRo5zw0`pl+`%A)xLjHW^TNV?sb(>c3m!>DrArHX%K1w8YbGus-XEiKmNsw8#!7Wi#2R&fJ6!%!aS%&~q7T^9uQ2#pgtpo1QYB=}syi1EZn zf77Kg`T5p#Qe+>z9f>!D#C?z@?h^f=E=gdlA#fk0jY_S!tEqOs@@Vl`Nd%L}vZPJ9 zpJDF%!dNM3XOCscH`gu&>r}eIPEte;M>AQ{E@DahZSgbog>MfUq~WdOz&X%Ifsv&; zDrpzSx$3sZw-bs0#<>#Rzv|&9v&o3pMQqPeO~T}<`bmh|B+=_9z3$gwrF7S_;clz@ zHPq?mbo3KBgEnk&>upAM=aq(&u90_Jh}`7RpZ)eXq(eX8;P#XEF2we?H4@bCCq%gS zODgx16K_5N1{0<<5OE-(%RP@ayxq{C1R3?no0ndqZ)CjS9e#Z``A@b$PA_q9qJ7Z4 zv&7vjcEf2xLceF7A)m8!UD{9lvt~jBr7+s2t>1tMBH6m1#dOwKOUFRtMJpT4&}`=> zogcJH=Wkl1bJFpuR@kGcbk5+#l9^YN!X6zmk&03nAkLOlI_Kwc!nvB5H2SS*actPL zC6&(kd7N;rCI>!x%L05P7XnQ;93qVAO+7%)lbgj9iuo*Y@ALzQm?dA~0E&x=aVOJbEHSbtFHN8?>dH&RIKggB_8fKPwIy-owOgAN>rCFQ=|`zy z)u$oGjYNzKJ_9ih|Gs$m%v5MWZf`UY3QZkRPb?hV`B35Ep{2rbbcmditYU`9$;5~u zGW0{5)1<%Hy(iwM+(Ls3{$JCe^d^_l`>Gy^ba(+wxQsk+RCgQE-vN7+snMJCAH(?+ z&qAsG%B@3GzAxiAB?uUDa62*1jP@}l+9}X1P{j9Ns{|#n5NJ2hi0E*3oCt1v- zR@sK_B$(0cN2FD=`wheSMzuKKY`4H=^4O&t&Nr$RE;Ie~ie3{gbDx3W`h-kS$XtUE zqA0TT=>m@DAG5q5%iJwXDz`$7;4<}*$^*4llse_H?W7MR-Pnfeg`~oHgY2vaSaUPxk-5Dyz&6y_gB0wkpuTJHvfD^4y>hg8m@O(_?h)C*V9DxyoTjndnLQhFM#X( zA8ke)4UE0rWDtGb7G8z5JBif;q}=JI=Lbw<@B#UzF;vJP-*am`ZXmg8e2rZI>U+K- zT{YtBDqyE{x<_8@b6Oy!iKhW7pS|hhp!Qz7(jK3=UW`+rbj{!o-;_Z7hm=(TJC6-3 z6L<62%-KaqfnTe>POp`^!~*v8hkgtryf^$sG|p+>*eBX@ARe>uwFPEd+Nmwbz4o)^ zvMs6jt`UfZgjTq!LDIoG(%;Sc*hDO>s(wJPl|dc^!#CrNej1|#yf2lpn+uHI-TdJ7 ze!++uZY}|O+bP~A(|>orF;{^D#@i-cn}fN72Xjrb!K6?Rgl0@t%EZT_^}8R8A_5{Q zO%a_FrFP0wu!7eE+;?F$xLndQjPvZzLnJey*;Mncl%zMoBnRh&R)eg>KNT&ua3$!l zED0Ur<>utnNE#qgPvTLtsh@0>g<2Jqd&Ergf-qF6hs5Yx+a9sB{Zwq*`(xWaXl?rd z+TMTqwx2qE+aqS%Pnm5Wl!Yd?-KqxeGjIVZs%pX~vgtUEKQfzM6W4T{%Eyd0U=#n? z#$gF}CTTie{^&HDfHUJ0Ln)l`kyM{JQ;*3q6I;H<((=Q|25rzhhJCA~d>Z#nX5;H* z<1dab1LuS-26iaFAQsKCkBqf92> zq1SM&lz1Uh`{8=o#N@A+s&2;Se^~+_vhsz~=jI!(AGd#CD8c<7M3vxCoz4F*cwVjF z8_s;VM^eE;&NAdKOsU)*n+={^w|^lzVz~G_;=7c8|i*9@@EO zqhkW;tKe~8Bu-CHNC|`t)tV=?^ebnJqBo=G&Bnp|1^zaAi;an_67{S+8*GDL$B5f88YDB0s+$We322!%;wppK4ymzl5COI5<-&CAg!fR zE+?|1ZK%G%6v~o>Bv(*Kav5ZvCd1pML;Sy=y+y{TwbY$5I}LBU5NHo}1<6M9a&GmKW#uat|3z#HuJEClg!j z!BL=UM474J6LJgQUqP zDbSNSLuAaA3{Ks-x0UcjYMY`~IAMW!0N>kUAFw<>;Tvs_p!9K@NU@ ztgLFZbC8Sp55B+sViSCCS+;HAO#E6f%DJ#au%pY@8Dfd5T-~TVHjcc`$Jp6s{|~aQ z$QVZ;pzT*V^W;;KN$di=m#A+1_ZJXQ40GIl?5QH|w<@bV%!0>PbOBYADl7W$Ggd^# z8`R~*%I{$RYQ(1ySE{~-Fjwkg337Aa6jJqO%wSKZorUNO#dQF3Q_nY~8^sm0ja)#t zopx((DTzt8i+s?>u>`Y^SY`^bB`1phLAu@Cb94~JS<1iZ+{&MjynK5^mVZi=54O6d z-r@BeT&DVJ-(Nc3AV5*F>Lk?fuKq>;A|CzVQsdYsr`Imfjp|%Dx}Y9|AqX#=N<8}Z z>Mx3KN${Egr|612!UDt+dVCDz~xUW1t&a6rn3S-?}9lSQ9Gpsr{{ zIK5JKkk;TKGtUQCF~Ma?zeTc4>InH%`Y6NgU+RTimc2`PHWW2BCr$3mHvrVi32K_u}N$fg8_G+IEXkv z)V;~8zh3ZuK}0O*Rt#w_=|4XSxcmL?j{@$4h~mr-=!e8*H2>h0*ZPC0qiE#wG4d_S zs%|+Id04S?#e&@|Z#>;pa!ns^1$c_CjODW`F&mY@2vsW846-YL!*% zS&)1rW}PlkUY*sQ7;;$3(=D z=BrU@v6*HE>+?S>8^Hl=1WL`{Y#e1GHiMnExXqyH|I@b9#uCP~J$M=T%|hB)XcBmP zA#X?CTUx?*8^mNT&aP!QV{zuhE>2#XW3e`o56-(!F3C#@_P2>IK{?_XMd`na`jlH5 zRYwcs?!Qb-AVKc`UF_UnS&}U{S2;#sCfqq0K~|BK8CAbx5$~Wyyh2J$*{Hqox zd6R{<;&Y|_VlgHJF6K4$H^jmFk$*?d9W(D#X9~G0_`AG%bZW(WML?p})Bbx_woNN& z*6M24k%i??NHpx%k867EQ^4Ir*0kzEuYN+S^Y!a{19d)^kUF#|eqS9CPDFY1)(i+6 zCzUl?h7={P0~8k|vnx3SIIZUkS;XR3r9x-V;!{X zk5YdcZt(0?0(+6Q`K>Qdh;T_S@-hq2beO4*gI2!x76 z2y`R0l_BaxC%Z2eofOZf(5-$UkBSds-a-LNc_ZJ;z`cqv?x{*y8{MpEAp?>`zN`2S zC$**mjIt^=8x=OvE3lsOii*AxTgnw@FC_h}g?z_(_EK`rUP|uSOL4`NlBA4$Sw_WX zE(lpBo!_i7OW}g?X5rZzRCM+RX=g8`^z5aS#g{^4m87h>(e4Q{`w+2rC+NX^ymwq% z(MWlneM-du&>NN7pA1AIzJl#yX;tJcR_=s!k{Kj<j6n%vakuH|Yi$LC7 z{->1Q>Y~5v>6L!t-KWfV=kq^h^wvT8`y0Mh#Q#+BKZX2HKD`m5zb_$fR>`a-*$ZCh z;$D&XO+craC1v8WlP{~pWr!{*b5)?u8K^5w7ymo8U{R6yw@dsl7_-ul_`g^D-%kLU zd&G;P_YHVi^?af{{_qIe6)*1ie3sZ}k@ET~ZG09%*E~8ti@d59r(z2}tW`FN?Kj6C zT;-IGNcpMcH(yT(0!Z>sg5{<7tnyuQj(t~*?@>Vy*S(iRS&I7@b+}A08)et!jweG= z*>Q1N5vb(-d5cB8Hb$k$1o%arFQ2j*E9Kd*iV}!a#Oa&)#QPiowoyFUTy(4zIpqvC zf1oH>Y*+3?IBI5KfL4XSCIU|s-=qHEEp|BC2{KZAoqoMd)N{w9nmG8ynOw4*M&E_J z7ry0ORv8LX$^;)@{w{mWZRT>zh_*$UPhrEHl|!e(x1K^V>i~Qvide@*R4I3+{TmHw znK(h5n0J(-ly`GURYpncQPMaXD8#ga+LUr&kpA0m;5~T!a}u1Qlp^yAQN?;xv2x&1 zS|Vv9$5RHf%qIpoUwT<5&1mMV-vkujaZ%(9L6l17{rKy=ALU@NIzg&}xp54iG=Vp8(i{=9LZPst5@V#iG#U@o*9L} zFEVWm_w`%A>?vuN|p;wIZx=`M07ez;Pt}&_yF{=OjA~~va zX;jf!Ul&&t;)?$8Ib&2qa#S&-2WdFx({RqG;hbv@=X`TG^R2@f>f==}`W2$e1Z|do z00o8E5mcodjCvSr@oUtka^Q@Yzt;!6KZ3MsLJ&K`mqF8=6q%3-KR=}$$%)%l`4+y~ zB$m_>ULL&bl=TvaFB*$VTWMK}4F|$SMLT0($uCh9gpdv3a&niXESQ8W=10VZn**+A zw}!&s1h-V~>&0dbo#(~WV&}yVXVa1<0U~>DHU=GSN_&_tx5(uW=4TEjC#_c8XLxDr6B8JwzOh zt1+9sslqDGAEzap%v}&)Fm()Gr)`f6{ckC1lIAABhWjZ>zZU zBH}jS`Bu(D<2BvauSOcYQ0ZgRX@ezB8gaeB5<6OWN3NX9%kLhSWg|x9svE?WKhlhe z{aqHqesL;1I1XvCk6()!{`)v!H02rymX|(|g25nx~7+)UzN@Q`R0i zPAKtwMnDqDPi3#%#l$W93Mn#py+cL@GtcTWxL6l6w-gZE>yQD#;D|}c0k4d&!9Fv5 zeDy~F$#9Ojbu7_VyYk2jgOLbEa^u1aX?&g)&F*CG!boAR=$Q`K-YTN z&Q{{NWI6hkoh?U;$u{y7eFE!{%o`Oz-l)4-lFq(GUzxyI z)sND6MN|3!O&}NAEf@6BMaV=|WMrb+5pXwYCJL6Bj4CCHg2kN1>{$G(eS~8`_A|~( zHJ^-bUZkbsxs9={<=6r2r+hh8|d|HCdL z8bA%QBcD{Yf!`66x49}Z3+i9d}IVQe1xC|zZukkaDrYxf*PJD!vdj~ zb^9wD0`4yH=77K9Ek}uaUx1<;#5a*-5f0bfEF&DwCB9)Zvm^2G4b2?i&?e&>b|uC) ztVDPf1-4X?VLBT0!hsG6Lum#&7(!5k24Ss4Zvy5|5rHb$3;-aV^-cJZkCVpb zE7)MD^g>%IsTcE`j72lwTr=~zXRjcIf~AvWbyjDJZi@Pjka^j(z-#Jkw!puh!WQ^l zz$GLVGy|}U&Gh*css8!T6h3N}+oY}ehba;o&L(3;mZz|Z8G~R`R(?vFVitUz z=kU^eqkj_PLsSzrG-D7O(|b$Y-D1PqCQf3%e!%bUw#M@~kwMRCCvGV~KAEE_Z9>-Qh1ZJ*Qg_n`8c{H9iF1!eDsH9v~_2U73yH~lXKK%9IO%AW8ec4sM+WMtq z`}p;#$~E5I{fn~1y&q(e|4X$26`+0GPc&?1c#%qTjtrc4wJjJL`?l!$$L zZ^3>T{0feF_16s)iW2v`oWCfhKezO8r!I9Z^P)@R=#vbyPub|x6-J*#>EhYK&r_$wlUGsNE3A6%R2`k+l!EPbGyY_Y zG-;7mmHk>of9Vacry_5@vi8~t>t{g*0AnP8MjRQ16dV<$81_P0C%1~KI#-lj=p~7x zct7DN(rsgr(2v;luaCL568R8F>Hb4#ianiz2jc_@;_{Zpm=B_OCOKrgIZ~1g&jim* zJd-v>;+gziVLTHuB#D11xdPVKPZjCN@gs$OZA$GD&t!$dtFFv0S6!9Di>!+MIPO+} zXA+wb0X;GCOy+Dx!SmM%G{s@`U#MOe*Q5uVMr%UlTSZ6)y4PoY&p zILv&Aq{{`Y_wnYrC{cpgQ!50Kz*FFVB}YWbvKwjU6A)2$CrLAZixF={uwzD|d1c1y z;hU4>ng5z4yruZl<(Zde$g-27jzR-{JO#Cn?An+-&i*4S+GdknC8(<20PIFt?{qH)CTJS`iB zBsp(U^Qz-0aSK!8mFG^0{{wV*^jzq$*QCQEJqImxnB}y>%UBUSXh@Tz%dm5jHNt90 zsPO2}Q=yEodSFoEM;6jFueF)e`e~?;ae0&(yTaZmERT&zD|~G-SRPx7P)$o#}E$Cda#=S2uGMM=?iZ6?3@DS51(SK&A|LF|a<3aF^oPa2L~Mtx;6+ zbsC3Eig%$hWV{OqGn3<8K$uWENXjH=9w1Db4kKB1HaLqhK4FY;W?v#&YH>`kOSBo5 zZl!Z$(_^|Nv3ZK6v!8*@Q!L|LPG24(7>haBrO*=Wf`T&57YqW;j1uUm7y{*Jm!>E_ zk31fU27b&Wx<-K}W*){<6RN%m;E!@X2 ztk*qj%d)W&?fRAC5=aI`WWS}9sFh7*Cjt50vySk1a$+68C^cLy;$TEimJ&Kj5JKFqI4)yiPAIGXx1BRe9HKZ7QQVp{)UBsYPEr;XBflxN>N0G;=2m;lOq$Uu+QzL=OA7BFol;3zH`8I>-} z4l#}|JOk0;hlZs??^-(a4mxxq zS%==Wbm-kPcIcgGhu(>H=$*t4Eg&za1M<)X2f_!uwIkx6d9NECfn#O2?8pk)ksR)b zXjQk_7V$-R9=dU7!d}!ZyTR|DrWa5Kyl%$ca>x$Z@?KBqglIm*{(3*~a+-mPqf>&c zCPRMnJVQYc%G#@CNXvY7(PAml^$1OR(W5A!X*a?#c ziK4ETW%fQpXk{T*li5Y#Tm(7S%OIyFy6Ns-!+^1=H|n@$2+Er_nfcB~QlfKcBW05F zY)v1t`?hSGb4L`U@YPX*u zO`=xQTm^?GQrNwjX0v$)l{I_QSfgMj68XEhvEn2*=&uQi?)7P`UZ}T8^}^$6EY3sa zA`V{+3QWj)2W0#8wrJ*5U!CHs%g76jfj+NS3G{kCS@xxxllE$tdl0@WZED@u0+nr= z5Mvq+I{ZO#=G0Be3mw0HoJ|bk^LAEPmwNSXue*ytPm44??a#MnBbhn&qqMg^f!%H* zyKy+jeAGWqob(+O8v?$@nf>|Wycb>BuDAxgleNW4?K14Ti{}e=rpN;r z3LY28)&81#XK?B%!OBvUcDq-v52ljg#og(3bQ$`yyL;Jl8u_)lkXE`vuq@K*eQ!UR zdn=lgJiGJ^pQWGXGjM5s*k4xHZeN-YR>rXp2}a0mTB!h5@I>*=0s3YqeY1(Q^BZfo zFWYyI@<0=7=x4VRk3xRY$Psl2XUIlNe$uzXf`grn^rq!+!ccDG)ZJds8%zCSt_tcg zorHvzPe^D*#xet>qs|n8$tR7r{yP&AErt}m?)_qAi?d#Fb;xo5C2?pSJnW+7DM(I% z(cfYn`WStzG5&4lV6{`nJClxo^jX6AM<4xwa%d&_>@4H|Px0?$bNn~aH#=j-KTJv} zSaSN1pM!%Tn0K3?Fns>hveYI7-+IG5P(?4agT1{iF z@hVF)zSWN%*lFHn zX!|`|m;Q)OwSs!FAh8^K?muN5Hg)8y%*)MCUh|rZvtPxZKZ)dJOUH^`_SOLW ziLtLo+8yEq!aiW0Lu7x?_2bm(^6J~H#`jz{zPAE<(6e1x^I8fU-wVkc_H2qYzW*cz z<~DC$sk)-y#>TN%jFYFxNaX#!lxQ@kva<t?c=*;FEP;oCeqb*Uj08Z(OQ)^~Yba zF^-|5J?!uTP1c)N=JRaxDS>ST8@!(FOLG~|a4!B41woxnrHJNn)3wU&-a4l!Z`oph zWuq3swmhLJ5KL{NL!?RbG(P-Vz`Z}9Z_wPG%AIdBeh2ucGRmqp!;#Kd?G?KSBEi8z zloJV!b}k$t{&p^M0F|Iv$;{f~4c=<^M;cUpqvqaK{g<9OVt9S-ZP+cGqJG3Q3Q~m` zy-ArkB@f;)qO3_x7^B+yrMHxbGZ&e^I~jh?vVJS>W-Z4kGSiCR-4Sx&nly^`Kj)>Q zC2uUefEE(yaz}0bvJE_fSob09RYI8fwrB-<&u}|l$F^vH=Ncf_vcKJt4b(WB49dx+ z8a_ugWD-r!Zjtii4y5ijZwRsB#ga;R0bCRi!}}dEynrD0yE~_eeh0r}zh_;vKjY2* zP;bUtd-Eyk5|uc~{{~vG|B^s@VqnFJa@M(e-W|XK64YQ=N^_&`1-Vi80-5hRke7ak zMCmLIR34|aS|PC(6k{!v0%4Jjr8VS?vPW67#Lm{zLSm)L?95a?Ws~O6o9rB(yx6b5 zK|U++9Lc8`=DvVn-X8yij?`8re+wSwf7!?zlrp;o^bZ2=@busmnYpdmAABg!S>itB zuX*30Ia>S;@23PD0|EV@Khknza1z3K#K#k!kIBb&L+J05C<6K>O2Fow(jWT0yusPl zri6aRzen=(^iWVfPuGIW#EJR7z4}jHz?VjO1Brs~>0WlFzp_=0wEBZnn)O!D7Tm6c za1hc&A%fjUxuTeK@$S-hmeRU68hMgW76DjW*lPP~%?Q=j} z6*E~@qQ6<9hr9qk#Kv}^F_~om%WU!i`S4Q2E_n7SYjRShfz6&O=dT|wQaWcKJd^hb ze6c8(8HQU~+2BBlv{S+HxXS0*vGh{ItjRE}>?a_hKDjY4vq7YJl@VBx5>cnjdzz{r zCJK>otc8NhMY5=1H8~;Wi;aarH1$3)=-0jWz8LY~V!OX`UrBIMD>i@zzuxS3x4@x5 zOlAgqHA(jbL0FdxRzlS~1Zk=6janVw_vc9sz4HEw~Y_G2I5@mXJEc-GABkhcDhv4su8zT)uuGKrm zYfJaS(Mya0EA1yESU>m-Yk-_H1tGTZ$k33yn>ZxT@{ss}n;t(RapM7io<_6-nzyFt zcpUO6#qHK-&EB9Y2jsAL)?j_sVi}Nq@gFvc4;}czIwC7D7}Dl%i*JSAAO^6WhC{IY z7?0gFfP!I*w744$OkC05Cb$|IG$>t;pf$-%E%ev4rHfHiJx7(b>jx|j0Ri?Q%7s@z zet^z-d{^%L%X{XPvs1 zRc;l>GnnZIqH@JUeq0A{x5^ZTo8pzLAx^~ki%mla(lpo(0V+N%9V|Qf6uyO$vxq-; zgv6ta4#7u*-#)5UwiRp-RJLHEoPyH~xVwlGx&#`vvW&g{NPVP{vl9a_i=7Ao=pFF1 zE+y*?h01x2DS&dX9f+woE>m{WWH2E8YV`Bu<2+`DcjTtdRa1*BJR{zC`(F zcxQ(~gib8*uZ|1k8F6A%RHH=r+dZRLr9=)DxY0F=PX{OaYit+=e~qq~OqY~D((u=? z#rSKa$M|c=mgur2(PrcHIB1jkXMRTt8_GXpq|SL+9@*F3Sj2uBSSL`(RI!JKmrytA zRre}RLGyFQl9?DFjN1+DgfRojXu|%=5W#fh6Cej^W-f34=Qvxy^STmTNL+qPoVk0g zOhwZlkm=+taE^PBT`);1ngl?Ooko%f^)n}Z*~7NPPmjkzxhw0fxhorb5V|mAd1DCk zu4%~f#v~M%L&fD-iZk&zjOVd{94IcQFCJF3$(n}dbw=WdnO62?WD7|CC7D)Ch?2nI@j&2hF{f*Cy&ne)P%5R5h#L^OAIz&+>_hok#laUy;YhqvVo zeizd>X*{WGq;mLURhimC=`~k#6vMpnFu|7t>ftT(*?rQEy2_HQrftbx)#a+biohZ3 ztH+Shw_+D;JC5yJPZEhK=8mog^z#ULqm5`JXO z-%$Y@LpPE!v;*p|pO9T1s=^9U$n0&=G?V|3x{6g+B+Dgps%MF|o-*6I6Q)mUYYE!Q zo*0+1f5qhVa3NazCGy0$G@WA9X6+=M!ki}gN*}qeTid%$w)a%h_7cn*>6n`=?d^OJ zF0|o=W5nNg7e?U=k(S2$V_S51zW|LB=SLYY${+-qOd}CvcnSLOdGax1q)Hm~36lw7 zZ*ur9^ro~=qKQ%|8RI1&Mb>!v8K;$WrpgKHlj$Tg4PSy$T$s7iM+lk~U6-TUCzC5S ziMC#bD7*2T_z)Qm3A>fn%1+9`3WfmSV~~e#W$g}$?~u;MLz(k%9_33U zhQ&p0B(4D2IY?gw4#>sq5O6UJ#m0KM1YfeT(F42{=Yg>qcEJaZ8LHZO$D(#YgCUE` z2drQ9>78DEpT5`7)&4ckR(Riv6NV&^#!rf+7WdYuR`pefjYAHkR&~*Jh5(V~E7%Yo zAsD3eMVI=77^ZI*5`D-%R>(OWUcD>ywUGl6)zKuiU@{bfwO}SR-JPWWTJav)Qze*% z4|=eAwEVa=c?4-h=5T)b{mP>^q>x=nS@YEtwnma(s7?-R-8=+h)hjD6jLBj1T7n+s z15DfRXxdf`@G!kbgK=TdXQ+`0ADp+y6@&cF-yQtUjmA5_B1fXP)fEvrHu|aX1!_$s zU0JO|sG9s7ZOk0)gI^S_xbnNCuH%GYKD`ao4z6C`4XsO+z8UlZ=FeC6C2!qjq34`% zmftMccQQBY^`Y%)8030SzY^$IJtvobjo$?0{)~hQS%7dTIX=2Ub{HS)n8NwF?i=nl zF=5Cv9jVtU+qH%_9g*yHSB$f{r9Bo6VheXbKd3*!X4|b4HKGN%QTXA~g5JD&xXKNE zPc%^P7mK}WGQY6k+XhLCGgF_DH&^gMc(n|kA)Gh~KTD$pA_ECRR=fzpfI1U#BADXb z&^R$5eo;(+B)fOwI9qL`dhBG+f(r?0O5BfR@0$o)uv#~w&Y`#U{L>5!JUkd(KhaUJ z#OJ_!;i|0tV(tx!zZiKsz=~x*Cj1Bj$rKdnwx2nf=0Eni<4wU;MA)ddbM@G$BBSF->W8^bqr~ zC6&gyLs{LN%8p!&%7YBnjZ4z2M?e}4Uqm2D%Ifv0jQZ51tp2flog*ah`e6{qKlX^K z#Ajk#f1f`aP*&e7KMO0XXQxtApQA7e(Y`~}F4sF@jQA;K^*6XZZz-!M$qJq$4-ixU zC!Fhk60P7}u0VX1E-Mh94M@y}m&tPlpY_StKjzm5mDPJ4{Q6PRe+RW^B{^T+gFaJx zTBtpu$1gZI4WP36Ia%;Cd5dMi`((l0T<}R{^>SJ8er0vFLt5*N_>p-I-qnB{^;gi9 zr$Z^(>*+>FfNHDd=eez*|) zhL(rkyzLZ-Ong&CWx8`Y89{&Lj zFZU-D`ZAY1Wp5Lo1l(PI!6E4zP5pUE@HW8=zemn)7xe?JKos-Mew7lrui!mz(}OpI zvM`{;=65ikIj0>vlS~d@5APLmjpz5%*#rS=bl6n*~? z(1LlraGd;;1IAJI>w)M|X3|EYzW7}7vpr##skwB!)F?h^KeIjRHoJ^w_S-p{`FPK% zq0Y~C1g;YP(}psy-ad{(ncp))ncs`e&jt}NMETi(Co#PF*~rU;KsgvQAAY@=DQ1ZB zvqPMMP1DofyaN6fa1m@W;%_-08}<&r-a`?CZ(12@lCEZ}6>MOxO`|le%9@AL*x_O^ zH2GC@=*~%#8~e;O<{`1Ukxtr|#+qkJoCZ3Xl*XE8Y-NJKylGaJ&`(uvU)6($>DWs7+dJhMx zv;;VRv^7x07o4(M3r?s>k~g}`nm3yBuvCmXZQkhgqa;nd(=Bl+YHVCelF~TQR;j82bB89gdV z%4o_)7Ls|=XrdwhsCtkm7Fs)TS|VV|3_%IF_xl_tO%&&JskQ6k9BDDpj@O=3w4= zL0Dz1`JrpgcofPH{TE_+kQPOd%e^?`p^V-K#8)>o_l1D=aEL*6`%)Cr<+@P{1FBx&f(J01{aqAO=^C)3^kP)HBq|jOsf(o zS{)I3VXOuS6z}4atQw$Q4Ai#?PT;QkM4RTk7}kzi3u5@u_gq8b>vNQh+Kon zRZeiHEp#GpP*wtzSE~_Qso0NSk_O(z^yEM7tbsBexqm2bgq}S$XqbHAMnaIH%cUlv zFlr^lg0kW!5NJZFs_%m(xC|%A+=62`L*@fFVP0Mld5-uiH;REAq$A!N{C1?o-R=!8 zv%^u^>VJmW9i;{D#nKw3>N{~`-l%k+4sWeH!0qi-m8pA>6vwAbZBsp+)s=W>!6>H+ zSUBgsPN}nQ_Im28Z(@~fBc$|&U#B0l@FTSFD&p+sz(b*RVJ`icMKM7O$Elt@)%UT4 zF$Wmk+ZI(7Y*6bm{+p6xfZ^Q?9%=0@5 z9QjT;e#pW7b5R$h8q~LO;kx@a&I<@u3k+_~&}KE+CH%;%#0VZ&mB8_W4W1KAv)JrUUXVrLLs11LJeOwI zt4Dn9EtVoMKHHXv+4d_Oj*+O1_$a`V*PW;3RW9yL)cix|YlbRbIYTG~eQ zK=cMGlkTa4#CT$eXy{^$yom8X1d|BmYd>>{lp|dFC@2P#h-Y(Vv5P{>Xz8Nxx6vHH z0nH@r{Ra*1@9EK!QCv5g{SrAP$lII=1WIm7JAyYJh)oSL2dP1_AB}NQaN5~Lq3~?k zL1asG*%GtWU>9-7`GRGGPz@9EvECbyDSUmnw$L2 z=l(PMD*TxQt(p%~NL_*bGqQ!070dZeiYU##yltGQubW&Ks>pR=u20Ax6risn|6eOD zhi$}jKDo(e-1S#(fjdm3p+w&$I86k~!o0XA;BE`Jw+e=IYouNcPFVFR(*6~H>a>4x zanKH!>0}u6Ii&rox2FAT=y_{M_|4sxz~v!eIOB>t|D=E2Ny4CUGz8b!%$Y6GH*1wm zC3>@%K{1bF65GgMpv~(BS7)UBn}Cjwr&N?vjg)^As1p;?IfC3|Y*I(@Gs9Lq%Cr@y zTN{>cI#X2t2W-VyReHA?>43Kz$v=YVBqg%+^W-U;ys3Q(LKJkk?tg+1H=dagEn-ij z;jdt3LftTwgt&1C^G|lO&%#g%Kf4V=+!#lQkJ-$L{gi|_l({E&8)UeVb5ClOPY@aU z^%kH0IH_gbZRDcxFAd|M4j>1wNc}j{#cJFI!P zAEya{B`3 zP->C%gaS&OoYB=ui?Rob8uDq&loAw--i1Aj5R1+j0dTxhWS;yIh(UqV1; ze!4LW*+DtJuTcCQ;26fg5YD19b%W~JP`!wlGbdTdU=K+Z+s@R6n*K;NB6LEFegLjy z4e>UBJ5eJoLXLhqpkM6yT()34^i50<)ic4axvUgH`gYQg=F=&Pz-pVgIz%&9b39W0 z9M`QkL@QQ34b?%CZC2mc@Jx6mO>iEug4S7%mR-crKt&sp~U$g*)|>?yL%S`p&24gaM>5UINr*So4-BYi;*7``BGysky(t%Bi&L^=48 z%PU0;10Y2}P?zHUQrcvRs+@V`z)_J;2Z-p~cR^wE%u)kU`ckF0RfW^|P^tnX#Z{OT z*Ja?~$l`s zro3>wI)nT*2v|}-PQK?cN@el_o`W=u5Ih|trBeCC2zJh3r{EkUMV zfx7v3(oyHCMdPCG%uzRH_ElEhwHU%cWtYG5lwS`gcrB0pE4-H9P=eo&gGcHh)+_io z+A@lIqaa-YeT(TZ+R6^29U7tIO0Q+*5?d0-<@3)Z2#v9se*Sch%k0Bj%8tvNe;!_< zFn0BD((fTkBS@_ox-5do?_|kBpTo~{V%nC&zRPHvp+wY-7h~Hdak>siak>IV@<2JK zT5zhE)7_@a@)ia-##~0T2gZ7f-k6w*z95;ms0oLWkcyr?j~~67kpKi{qZ%4*61sU1l+G9UW){7_vV6QKK&!42NZ1Nam_@z z5CXz3%^lXDs9X~m?=(N1&i<(0&!sbu^bP5Q`dU3RNjRae?q5<-{Y_P`MI+d;6X^tY zvlA+t$cu^Tr36$KC)A8ls^Ao*dr^z$FXjm&R@10Zk@39 z&Iau?F_QcvKq=xTR#$&tv~8lLZ4x8@koW{JCkNnh(edCMcK`flV!OAZYM0S&YOrj6 zpOGm*wqLaUS21nJm+J^=Xf5khC^;)IHd0@X4%{VuoYl}tY)b`sn$qbE|I)X^%g`kD zFC9g1QMRoKWGKih49`U+w?n5(2kR(4ii-`8)w~=!iIJ*HrcCRz0KcTVOhR{=Y(=37 z`+`)2G)8Kq)zH;zK*BEzdp&$gDr>(#5cSrC!v37yHTe|KEEp9yE4)!ZO;+ju$8qDl zU3WZs-bN3Zxbs!2Vn_W25m`Ys(qzWdr{Y&Ue9NYy+wXvBmrMzj`}}an3RxqI@L={V zK7%);+H9V^%9@5$jwh@0x z%4aJ9(*$pecs445jh>FBml+YKxsv}dGdxF|rWrV{f*YU|z4{R&eW};oXJjx{?mT8D zG3BTsGmkHyxK%QzmQ3Nx342w2&|kS*{C=BL_XhNRfyxc!PL@x;E4g*)e$O`cXYuO| zRxJKZc`GR$$X0ejmdG`271tIY9qi>>88l%wGGA5f4Sns{SjvcW?8R}M-XxqVUOb>x?jgxeRFrv%=Gmb< zewCf|+Qf3YKPahuS99<4N1DO>VQuTj1HmhIYlw}_Bh;-KSJBB#_#Lz8QV7EvgWp4z zqK&{$^VhV1n<;POp{BX0=#-FYV1TUVTz@k=*N^tGZ3WjIi{>} zNJmE#ej#LWHy`<0LVWFp6c*9y4e{rd6dpu!q}~iVV+je)ECmsHtgxlHFtl*hV8pA3 zf*C&SIgWPE5hZX$^&DF|QjF6Uam&5MkXY2BGJ0)L2nXza|dw^x#d7o~Lol z`0V5?1J{G+em_oZz?_h1%1L_Dt6Ft&!{kaU*!6gAC@z;R<47DUF=p zc$6(uL8vfBtEf$CaApMU@&ddCO$+F41si1BTr_&rIGk7Fyu){JSsP_%b7X8`k&Wal z8ZD~TEb8@I(ajJ_`jaiDBsh2nD(S009{$&Sj&@@TRfjkgYqu{&s;ca-|8ATZj`otu z2Csf1;BEkb8KTF68JjWU0Y{q<4FZlW`bNXvnL6GdOr;ncq%1Ap$a^cXp`%5+Mr$=q z>9nzKrxAVg1crD^B)f|)n8duW4EFqlycWbeuNm*WEH3nxo`;PG&lwMXDlSyLwWrc} z@)+K1?YZ4_q^~p`>7z5l_;PR=x7wsHG(8RT4a4-P6gEuTjeHM>&tjO~XqOA8AYged`SNY3obQ=~nx+|1|b#{^HE`>D+TLO7_-5YtLa1n2*PT1c^(*ADAzcM~Fd&OXcPV!(@QRo*72O^HfxDB4-jqbRUpmwTM z8DDh5N|gY*nnQ-}i1O%F!ii7frRu~ZEf`XzY~hUXdw4JoD; zcyX;r3#RjK3>Mk@lRN!O5#iSI5 z4RTZ}Ge-k_!K1$(hqEFe;Vo46DI61GTjXORe%n;_J?vw@)i@lGMi8+&zZQdlNFb$l zgcyp+k_T9M+$MKlnl9DdB8nFM2*8lT@8Ix3(FGf*WLYkidAnKW|FK!h{G~0~j_z}R z9Ji%6iK4lrf(@aOvaIUw$Z{6kt;uL7d?`*P3bdnnr1GeV)F46RvXLmL`_)0QlsfcK zB>OJGT-Cl`J$mvr3vfsa?jusLL2s#3>MrbABAv&KX~CRyM6zAt6TPjcVlWB_!jln2 zWScW5E}w|0pH=UW9=|2}Hm|;+L=WkWR(B3OQrY46L1Z&VuAzGk5TaXCr4P53%7G0W zeig_|*WfMol~mGNqq*iy=dNz86oan8S zI&M$<(k~hVZBTu?r21%+1458Xr%XH?L81QD04-Xq>ie-EswDczd?6ds>J#!HRz_J# z@LO~Ml=}1|Lbk+a0CS+&2WVd?(Hl$LE#QTUD9-Db05htx`zw!^1oN69F6LwFatiq~ zz`<=9gp#GX8>R=ROq3``3|ZuEtV_+iXC0>}S3TWIaI~~W1#&~G-W|+v!F?F1TYG4X z+B6|q8Yj-LiQCz8b1*eG;NI=8IqZPGmv8|xeiDm^B90fv1=_e!a7_Re+NWytwgjYo)lJznL;M(eY~!CO;E1uIHZl^Z)_N(`1elpFV%P&|k2)vJ(IikLFb zR>mjF+L+Le2Ia;k0P-~GUE(#uCnEiA#kTNR1YkWV8q%qgUnL1Vvk8*<5+8=EUrN*7 zypS(uxb`Iu*N=I){4iz5kCq&vF4+3eGIV-}IYI|j9wPJ6&gc+@f&U~mYpXa*ux;@Z%l8pGq|@!8Ge6INhyd@$3izb?dA)&auIxtqL&5fLJ2 zRUE^6_!xd8JXX_Z5E8L72nGd->BTuc;p>1pGA7Z( z+AL*lK&r*+qwFjsx_t2CbcU1bVi}zfi|J8p9rANLIWmUtOc31mk7U)?L7P;4Z5#=5OuO$mat6R;^ootufn<#{)NIm9 zSNFNPJ7k+xM}q}U$yN8cf_m{xGxTFR7i2-(;nw3I-m`ujaEpx?2vy%_VC=ZNV&HXH zXU_&H(*SjUp3{p^)nDZbKPX(Jfz5tawc#lWHfi+-Zx%vWeF|Irpgk-Jrq)Xpqa_pPgc@IP zlGGzgJiAih7v2lYb05W`Z5CH5JX$v@Gd>dkbj!iP4w|HM9b(L>b2OY)gkUT>27@3a zodv zaDsKCUJlNihhQQ>9Y3{6BFJbmwaQ{e5i(^abdo-?p0Ka?VbE3NO&6^S?7*ZS=Amj; z1Zyi+k87`NXgEnj!=k6u&>l4OedzHhJVA?~n`IyL@y&852AOczE{RY}ktkb;em4ZC zM(ReY@b0#WQ}^*d3$g}?W~82o*#U7ztb*W8Iig5N6g3SgcrGYd5U>yncSPz-f*Bop zyP#ybb%<_ygQa$pp6^AkZ$sBUruaup26i+o4mfO%VbOutfwyj$d~^F0{GZ*#_WEB$=B<)=)%jfwP3-AiY3?*aa`plnbE zA=+0qx{r#jHqz>^+%5LAHh(a+LlF56t#Y^5v7O9$VjmFYAEBKKy7{7hJ}Y=gTt}iU z-I1iFJ79)QCFQkB>D?lgwfVd?m=!*B4PJ$R#pok~X&dexXS3o(6l%Jfx|;6Q_c0oZ zrVEZlLSi7|lOZv_@rP4TOB6RSA_Br#_rdPav=cb zm*|?4>JoQ^R8y4B)!p#LK~z`P6jQ#IynN(NMp?3O7fF_);6l-l&~-LyxYk7YGIIm^ ztxlidVMPi0J`|_wJ7go*$wroPF`B9uJ5f{-+K5K0dJ05nv^`0apSgMf70opi{GxED zUh34-d%hx`NPL@8?t!cyjS=QmE=`bv^lSuZntu+)*2eXD#nAJZK2SDb zCB97@FP?s701`x9tC}hfi7hUv$RtE|j!N6{aLsc<--B_g6%{KDYJReNiNCeV*I*)Bk%Q?)xF8 zpE2_WlhV(hDhDH-f7XEsrhI}p-&%ON1mpctmqRvP-vQEcrf<2}UmbJexU_aEPHP7Qs3 zihhuA{A}tO{e!{tIq%L?-}2*0>T^2=hO54p+7s7D&&{`<*Li-X`tpw_(nrt9{$Z=H z>ggo)-Td^h)i?KrrfDww|f2CnG~mukR-rU(fr)HojHgNU}cM;JLD{bnf3 zUpJUYpF>%G@As3fx5xgc=>N#n1n$&A|NlO9aH#43;44Y!@vpB$hKoMdwI-}jG`FRM-U%xoXeBHNr*yd~f<-<#VFI|}AJo)K?VLQJ{ z`;wfO|I#;X^%ebMc*pPH50mUi&;M}P_M?MWCz-G3uO2q}er^(eyT|?5u+_IdV|dRG z=Tk}g6Mpi4ihtU6SrY!q;J=_lkii+Sl_jmC6l*@a=(9Qc;&U+S;_Q2 z6~nf^mw!LWdFlE7@X1Rh$>gPyVe4P-<4N}a=N})o{ol4bvA!YY-*zS$KhMr#8^7GI zCZosehONH()@1xn>#*@Vwtpm{|Bk;8AAfppGXCe@VLRV!zfC5;{MWF}pR;~=`JcH; zlK!}r;oCo^3~zsOClBv=vi|v`^2s^aJ7=#QOlI#Sds~UVEuj0OZUV*;?$bxflJZT% zn+s4&xuH1^TTN};9%*lTIC7r$Vbh-XYGV?6UUg&Co_7xY|NB6E|Ih9F!Qyy-kn z^j+rg=Z0!uJrzIlvizH0O|&nZd-=OEPgnj=GvoLBbD7_*@$0$ge{GFFf6t-3pTCeW zU-!K*kl6pvNZ*mp)9shP>`d4%KSTK+&P^!)yvkD^O_;UwD(}UwC+v6URo>QT636qL z=6h}b`JD$_W}o|c8a?uiZmEI>L@&`N7ZrQw{*y+(qW*L#`yl98{dsN`b?ikQrrVcv z`N~DHrIsbB$t782b>k6Ry_y>k<>l9r6B;#w>R;46csduJSbwxFO)-mYlcjxCN&St` zIN7H8hL6!)*|;(zFE_Pb)u)!?Akx^!2tG$e+~}$KJZMvMx!qIQdG0@NfZq}4_(tp< zw;S`x2VQZhPoI|W6Ae)H+r)3f^{$A%p7)LTl^@ri(ld>Bltzn;UKPq-eI=U$xBY`JHzko}qo8p}aS~mT-QW>$&I*`-wAJrezz6mW@5+ z`;YSqE=2{F{6a%j{$({u%O7h0*eS{7iC=mWO;fR-k<#_IelFBqoJdZ@!yXm&_~OI zR(^b_{QEDNm|(#wb?VbPZCA@sYprO=B6x@>|^plkhtX6LH!mRN?Qmxv~ug5JVNXVLsY0 zH>M3`^i?Ufgix8M714c-?&s6}BD$YT_X}l5aX+8B8`BlNPRt2MiDMY${y}}Jf|EZm zl*-`lZ=(+i@t~BleNqfS5#4pkArN45>L#i)TE=@cy z&Z#{Qd@o-AbN2RN%2ex#AKeZ~;*=6WJzOyeM$pD5pPypfRdEb&J3C0jAq zD$|ugh66#C%FSMr!d#IQ<_e`Shrp!7vp}7kM8LDicSVWvU2bSfVJrnCiHAwn{xaGA zQmep>=H|x5#+{+Pm;N}36dfzyEPdj%@^kWd9g9r==|Xc7#ANp)5o@b78IKcf(|1-_ z6^fscf62bf8h`j%*ULW^e1`r0Q_}m?U!Oc1z1RN=ubj*Jy!zVni%0v$1w*>uY<=N$ z`o`)PBA=4qE0pt_Zy2Xc=82p9{InQBwMUB9=)s;~AprXKA%c7u|JJbjyMy)}XaitGZ7*RZEGj zy#G1pzHKJa{(gTSlR5M5JLi1wcJ8_7-gh4zg9YN_%DgMXbUL5jJ08AFJBsO=O{l`} zYLgDn(J3~qEuo(+r4u*}c#c;D9^w^4r?JNH!ezT~DWTGP2ZZDoJENu31M&Q!9(WQF zz8E>iIp zD|}(Ghg;UB%@Gy!RM@ z$`?%qqeW5lW*T&K@}8@Q&JoKC72b*UNK&+2*tZ**@FY|r#4Z?re>+lV6_1Mg@N;Gz zm0fiUkg%&B!tDTJim1KHWfP5qcudmrOM?E?_hwzfnS$a)k(x5WlWk+hmx&c_{Z2^t zj)p8IEx!)M(!f#$yTVm#5Gx0(HKoBKQ4UkvicNwpcuI@gtg@?8D=|4ttAki=vve zL7QZR*2t1vVvw^32^m$hA|j`@#~=V5&gpS>|9zV=rV^+c(vrO}LIm_7c8Z zOYeQ24plBkdwW>Zz_Bu`SKSenJ($oTPA61~+~`;EXvYI0*AEDh8%jSbc0;9NKZ_a7 zi|u*5=ElSOz**R3#_H=Hjt$#UZx=SfLmjX^pM|P_6MHNk&dIy=_d|{0p@u#1><#$6 z8*YHSH^Iwpeg1c5_F5mH3n8UkKMt;-)^dHucdV0@>nBLmP6G;_R*W5I6mph_;z6&P z>w{G)BG{1Q*B37lE^-@4TTSTFWx0gLh4e1}$mPB3H8oL8V6X?$ij8I|sjn zV*Wv-4>H=57Q=pjguhcEn`sez$+;LlAr(Ind_oCN@0$`uklqe{B&e!*Q%VUGzZD_S zo8hljVZGr`sQIyFXqV}Qa=feO@okYhoij}2rhy{WG9Scp{Q)XOY(EMT4&v`{_+1@? zriv}w!IB(ID-WYq)n*begtm{Tw!|BJdfrmuXqr%bkvF|> z{0fYl^Utnq4A>szyC#hvUV-t}^M-->@ip^qP5N5|bGd_E4((d(tLJ?WeryjSR^)-f zpl1-XOiZ3E>m@csAXB9GS@xrz*NubF9~yq|TzqlRELEs#U#G`Ls7BU?uQ$4geYD#9 zvz|tTDA%nZXJaxPyl{vY%0=$}*RigL#%2ignv^l@MXWtkTU?F23?B(`L_lGGh204U zzVFc#AKraS1nGM3Nxf7-Qa%q!zpMA4ai;!n+M!(#M>cU6e|@d`enlw25c zKk!sXqA*4SJNlZ_LeldP$gJ8VR8toiv?^tU3#M+ei z42tGMs(=iG8yDL>4fpfzrm;55QAf1Uuc6&iGONDO+YnM>w-nC;%UT-e`yLttjCNG$ zuKBULg-F$hMji&=!PMrXl`@Z6e%iY(1V>mXy9W7Zob~u;xEsL9Dq2_OYX33bO|RcJ4C6g-$0p%x#+Lk1tjD@# zmfsS>I+SqWP={S1cx5kiWI*moyJ+26g}%W3c$Pi<6uGmTuuX5G*93Xio}$BG4}ft6 z`?ZM~)kkHc>N!WLbdzdW!FG7Op=`zwM{fthSv-zgYe;lK1txm1$G8hn4FaKy)}8E- zcN}{P@P*pmHFQEgR>P6FYWi(MirCm@d zXiE~g7C=HPZU`lB@bDqtFX;^47hJP@|$IrzdeH$$)h zz@*0G0E1_)TfU!F&3ZohUVglvGTx5@c&}`!o3|9(Fz~BpC?Wxd)=4XxlB=0eS*{-j zG3p7Qi0n>#c% zwZVRn9GW7WMyCK~0u7z%yvrB?^2GKRdx@r=#bH4@M_kX$uwb2|P0wunYOddY<8N@= zV&=y3yMlBS zP_wMowEL&<*$sd%82Ijd10vu`)7jbO`gsD&XSsehP+*}csm1JJ_|#C71;^ zdd8ve?4aV07+-cUp*T?=0ZA~l_)U()_QB7CU(kL!{e7DLrqkcvFW}#n^tTKBjiJAh z^!E?Cr-uG|;qUgKGI{x zjeGb8?0)|%*G@@;&z?B+A76^3Ia$2O_1vT!B;JVkB#Uy3g?n*YoQsZAGrZl^5!|=y z&Tw)~&6qnjB9`M+ErvLXS^*sV7^H9cQ?pm4116?we*{@pLxL!Le{JzLnyKo79F2Jq zw3eVM9CG6^{FszU$F}b#Z={H(*?0mqdb;8U1>RE{bC9k`$h1~(?! z0h5VJyGMUZj6!cU_QgmY9h%4s;Uf3qMlvQAUbw|GcVE_z=FaXhzSDrX20%f8jF<42 zhl?Xoz`klDD>13Ky!aDKN)aP+xy;@=%^<~q#p&VZ)tA^WZovDsY6IdYR94_62jr*| z%TWiGqrscBIXbw3a?}y!g2>TQPK5n%XCV}kT_^L72NB;zkyAgc;O~wi5F~; zGwyS^iMQ-`JS1|@GUSlCa-0Li6QD!X0%1tP;g&b?`=w^mL$cIDG8o!=ylX*(&S8RI zaej&Vvm03hmaYWGrvM9QK?D*W0Foeb;S5LzJ58dXdmtK->s=QV8pxUY`lOy+5cE$_ z(~z7sRiCS8LkxDz zr??+YNrO}z5ZePrA188aL5zpeR*i2J-%R-DcKDp!%d`TGOWMcss+Jrs$%0PO(7&#v>cE{j!51Qed;ZZa; zce8p11&N%1rjj#mqz-e|5T)B4s$-r7+ylip4g2X1a1eW>#CCJGN3tuB4>_BoA!n~t z35#n7wF6c7 z**{d_2Nin`eFJ(Hv;wCl28LbGwUhdv$%C&0zP3c_j7g^aBr2lb2B8qI6;#fPzP40O zbu_erF!cSCU_>A#S(I!EJt#E zsR?JuezW)F6o{skA5%Z?J^=IM85N!C84@&^+7C1wKcu-jEmB9e-BRhuvk6Nw==Ceb zj_!6LDI(z*w5YtHpf8$FG&sibVkzpjS*mg`_vN zRy>WaJH`AASP&631{heQqu+tU=RupL!Wl+4z`Y964t9YDqykum$~-G#wI}Kvr=&Jy zvsC6@5K7Akzu-oEHk{IfO>kaFIA*u#!`74ihI^=*;ae$xu!U)M!JR>G3aoUrO9S5~ zp%}}Avw6VdNNnurOlN18fS%&q3kk(#k`82zWIAAs;I^zo9mQn~ydocDw)A`ddR@Ge z>*Q7aFRMZ0MWo`I0OUKANB^l4409*rZ;9YOZ95{lPYK16&lD{=N&>z?M|GHHtdSfh zZ`w7SRgk}AtkYfcE+CuPF-UQiet%YSmi&CB0iav{JPM!g@adzuhM!uM{om&A+5?oo z!m9r=f2*FA^Y>KE|D3=2b>^=N=$1b%;lsh_Ud=W9{FnI~&I^N}x0iyS=SarWxgFi{ zFZ`U>2E3Q0tI2yxz9zXY9~4FE)b|5PIsE#$NS&N%sWe!IbFV5HHnGKNpJR`Z;vvf3BaYMRnEB;iI8C`STfkK8DXe%{Bb|zuVv3 z&XhmH@&7V^CMAF8|N5Wvw|rGy`Fj`2R{p#IpSAFLO>+%D|L^>*>qPnU{h^z1Y5p+|BL>Zkctd!a01w20AP_>8KXDy&w=qKH)mP z0pv;Y-i)i^PI^63e_F!H&cK&A7L$~$Oqg9}q3eq;1+lqDWWXmAK3O8yc9r}FL`Oed z5f+RYx@hnd_-JQCUTBpN9IdM^q>+uQ_YE$(RM>^y(IU6S!=~bHeFWUX3t>2?Omk^Pb&eYJs(n>l9;m3fY@8^tG>=&J1KHg zlw6zg6N5>9%M{@V%m5ykhq0iqd5>|}r6|DoB_Q_!_aTw%MoeDph>=(zrh+?E>xZi^ zvgu8WOVCcx9XECeiq_HVsqC@Y3CB|0PsC$5QE6EypWQ>8LgUQ}Oah_6o$=L6nQoYu}^a{5KR%>R)CZHq9_{@M@fo z?9zpz(Dqb;kn!_03DURv3zzOp@HdH~Z9#vr1v_^RIL*=3d$7fKzIfTJg9?CxfiiA@ z@!g3RQ!G~(c7RIbLyjfpo-jCD@j}BByu0NI-qisLXC-B!H2|!3p$z?YkYVvUyLhsa zaA9-e*L=y3p(2+iC@pZDyR!f{37wZ*rWMAe_%-qCG-033aw4w-FYM<_ehB454%<4O zQ2jkqg!Lltv0XGZMw|P4kn_*9kYaJKyR!w@d%JV@8Tz?f?&HM}Xo%~tNL6&)hA*(3 zw?cjuD!AH0MT$Ffy2xD-SPM64q8S-(ye8tc2k06wr?_!W@B(dzu?eGl*o0|GHsL4h zczp5Yc=a6twbql^Zd+csWEH$HWf5+Q+^OYqP|cB~HpVS;urZ#1##r$uHpZ@Q))=87 zyuMs{iR=9uoZAaA>Vl{iGgJ|PD%vK@jsav8$C&e{+(`59m^FiKddH@`-J|=Gi`c8bNf9n#3+^Yi z;E8Uj1=pijtY>38h3bU{Gw33=<>ZTUTkd|5wPh9;gUO=2LjZ4$$whzgAtJWuDZrQFcHDs+x4eT{7N%o# zD;Foh0XbC5H;H0pDMAU%lPoZo?S#sqkv$-A&5#6}o2Wyp*#LemY$jXHa)T54L})oL z>R8B&#dvBRx6aykYQmWm&tNd|r>#O+YQeFb=(LIZ(?TlySW+qtc?O)S!(Et?*AXV} z){bQxI+h;2ZVf5J_Y#LxrdU!A7#8YN1Q?a_qEkE>L8+E+a=+Fj<^_vvnp^96wPC}V z3wBBP9h$X~28BvzI14n;yMf2B_cHSMCct#NFgc4}1!x!MWZb*Yc&TwsKKcp;{J@qC>Kc z)$p!ny!3|Fz~SiQrL~8xmzT1MKn56Kv=#U)ZoGB;c)u6e1dLCb4R?0oGW05F7^hE| zVKAwrT@YTm+$ryRfaAmJ36raA*iyG&=xmwZFUMulu@#n6qjdK6!PSTz3}n z!-mRza1ttCC}NHE7&g{p*jO7ZReI%E+&Cm0r#)06_rX%7Y9lcxrh-xucBC3Nuu)b| zP{zWQte%pxdTp`fWp|;n@WM}DJ1AChiBzn27E`f0L~hIy42!Q^BGthe2h1$E`=INl zAr>g2X;cF_kHF>lFEIyu>GDu^xgEX_G7~#rQ@Ek^e(ds)`VdQn`YiJ`)|B_S#k7v| z-xW10Z$UrvCmiOMkX00Q0k>$^#EsW}!!9#(vC=Bc31>WgMc&@c00^DNc+C`J^+;A`vvq7d0GN-@%2M2aKImYn-Ty6Cmt#KV*Og8TbCSkco zE%IBongKt-G^pKb9HzTl)k8D|XPjd+Q*c@sGLBs=)zFwlR6`{qw;Oppt%i^e5W104 za$X1YBmO}1RYszxEuqUryr_?Z%RV;=t~JcowI4}Xl287fH&#!JV3!^ZbxyG3lO@8D z-E1es*9(;rYMsDd1HUnktspkhWH>Jm>(}=P7#zY?g{FQAR-Ckc+lp=*0k`3%g+bAD zW4X5vwe0KCB4yx4CvBCd;ec3*tksQmy4oRzy7I@<^7kpqTXl+%4*AM9SY1Dwtl;3A z^6~x7Kn0ufpJ!B%V~EvtGt_YpRLB!l`rOAqNzuKpaxsV1pXylnxe*vLkz2Qz6-Az1 z=pSKo-Js$a5?xJu+dW*>5730}Q*Y0bZXbxZ!(}jD$9zx1jrA^KeWn6Fqa!bp9=JfA z#iY~MFAdiik7MDV@byG-@g%Mp6X$44tR8OW1#BtCt{Mq>OoBmX|F=@s_P;^A9rSvP z-=J8r{jJz@>G`8Rf%54c>#E~f`v-o-9w1YF+2E@uW@7TD!%yj)+t?9AZ>fsC2o)d_I5sMb|G zw&Hl2P4L2k92}-QC)6ZTIJoCD?nv4#v9(5bGYQwHW#OC&_nkH&v8?hjAx2KPIWJPFeoW{gC;Y$?lH`OWIiZ3j@Nxn# zC%nfJ(&dD7IpIGnAwy2ckP{ZNgiJXhQ%-n-C1lG9*>XaEN>Ca^6W;w6(Nc0JmRzJs zF480$S@LF0@@7r)pE;P!+D#fYCE;q~Thw?u$?LP!ej3Ugp8N`SSQ;%3r3#{b+V~c7(ey zpTbIf9PiEmS$q~|B!n01@$Q@$_}v3ONxT@wyR&&9)8UiNyR$NQF;#~k8 zKv5ER{~X@U%G%A!)Ge%MUz?@o=}rb`^Ui$DT;Lxa6=<7auEU{0PncI%gO>Z#5+?sN z+I^VLwaQ^bK$x^K!|mG4+?#;y4$}I639dc$0@IlWz#I?Z(!R1l5aYyEsFWNUr^bFK zjZ>ZG_^m>T!QkVxdd(q$s+C3X4`9Zi z{}_wA;23X8E%o=&A#KB4RzQM&)!fKh{<~n!U}`h!o^Pw=*9P+2lk#gE?v&i*)Y*Q` z+`k>5`~K}W;Cmf{UfKhS-R4g3;aAv)X9u==xxB1Bq{hIBT($$f_T(Pg4vUbJd(I&xx!(VbS#G1V6YD4w#|Yc!rYqLm8){guIV(P|Z*KMymPVv+AyS1rN{4 zx7N`F{|%lbh9}7n&-z(fJo{%8o{qmtbD$4q{u3Uhe3PW|Z7Eq$k-OofVos`6%t?3Y zTQYu-mQIZ?h3o8O{mVTl9Zf&X_2bAHuYmdg%(^U`f+tWjIrGkH z=A3VwqP9P37R@*2G68GBIO&n$+^zO@1M@ekww)QE%{D&_Ne$#%~yY5!9gFXR|7 z@(@F@8_*m>m|$YbW-D9-!)W1!?|E^b&9%?KTdwBr1s_7 zyLD3PH}z-GoM?Yw`y4iWgu2PF`&hqChP?1Qg{`V=!bOpb&Q_fkHZf2`2dzQl_H5EP z^Jgr*!xT9Iy_cVc$!{Ky$C_EnXLo@6il3(Y2AztM_7Sq1vFO|0v&tkIyLXdL? z@XeXZ9!U1+-@^RBt#P!=5a!dg=s}wj!B2&(kzU-rd_E-P=0?r?Ms+*%BMtlK(tadj)UlQ?=uZPd`O%j6t-yyYTq` z0Y78fAK56U+N<5AtLc7MTR$b6Ru+bpW3c1Y7%W)R4BROy@5w8ubcWCtIIRZ(b7c6c z-u!j#MYMnJzZ7+(4lM6?ni z%PCX-)0CF1ZVYXPTUcG{7S?cDUQbfDur{|U8A!qf)&t(fCq=RPica3ddJ_RJhRP{6g{~J@Hz67eTN5fFQnoI$}^^wuj1Vvo{W%Yzn=|!`Md4MY((MYI$`&)=v&I6JG> z(6nOf9eL~KT}&W@akj4d9^vQX8^|C-LGtbhyF1BXck>bcvo?L{7#6ZzLV6&7t?zaj307S4&7!39VY)-g5ox^S&g54Tm!FNy@ z+(`rWA-izY_o+62a;~^Yk}qvN*JB@PBoFg?UMTVXa@X~qTK$1-=~3b&(<$C;7cw*K zLUyKI$jQQm@V*)F$%Ic(WBg_Ctdv2E9{bYvSJvYgF!Qra|*p{Vk5X2+oER8Lhrp%_{LsUql?r!XYSMzl5(7HK0|4<+`1 z!@CvWH>|SZkbsXzcdL?%#rGs~!JYEf`IvEa`G4Qw3?tmrTb>jF$at{dffzP{G^Rej zZl|>F__Mxe7I|ffxDs^PDtLwCDdb1vfec)PSs=ha+%Z+GP*oX+bWq5?CAeQWDUnqqE(P&&E=&6eOyz7Q&D!{D`!oZjm zp)y726N3-RlvD?oc!RC3AL_Zz)l0EdmSEe;}~~`l7|wq+<*cqD0T9@ z=ZdZPSX2tSQf%?-qe;qIZ#^0seeH2*_~k&rF2Vc1~3c1tL=CX|(UTNfNlF{Ku#n%*nx?P(aS-o%UT9ncVM zuF_D@VVIi*5?#UcOy0A9-?O9NbpXJiG{CPn`*D7Cu z8+6c&`|F*NApQI#d0zDsQ1p-sZ_(ESoz7;wYhHxT@eQOMnJC}2cOu?p(I0|uXXIs5 zge}4}qIj~YOU3+?&aZv%Sa$|fB%}R`iFJmX>+G*~TClCQ3EA0bJ16k2JcG_Lniuwq z+@q6~<=RR3oEZJYB`J}+G%;`;cE&_DC|YPHb4VO+@PCeRAf`jBYy}O&#Tv$*gHhnb zBt=u>D4$T*a;aZa4LJJ)isTz$l2^OBJI~lZZ?p^01!1`K#bc*w=HY_^ zjs=0zih{y76&@rO$j7=#Z2hL7(p1R8%Qx$Yd$HfhLvWdsd5`h6Do$4&uG6e*1~g5aK>vE2813EBQeFa<_w^0U zlzl_CnMB*R6(70O(M0l*xhtSR*r)ZBP5HQ0-6r&ohOl z@oxrRiT@brVatCg%L$p0^td1%E@bssB}c*T?jLPq8$1R*P9+NVf|VwEXYqFB@uVks zS4ngcKA&Xue?Ex`3VeR8)X`YZ`}GO*w2_i?M>EhU<+^_a(o(Mb$J7eavdol#fhiq_ z@JdVt8>xbMU@Y-_BD@VQDTi6X_~3pRq2k(U8+s3^6;S75`;f!s`d>kLn^-rUO3@k9gnrTDZz%4(H$Hv5f zG1mc7Cx_>-B}H5|W$4q5P0)mg)9u`Z=|AW6nP%&5| zoG#ZfBmLfZ9Ct%7dUCgoa{oMEf-+78r&p-43t;hXh}^5A)ezbzG_oi&%B~1iNH*Z2 zoUrX20{B`Qc^hK^e=Ti}YMJ864oZcB>g=6Cr^`5F@sO={n7`(DotN&euzE%WdvCGx zN)j}aoQl!4=d&M;W)r7Gnm8qZG4#c0Sk^+a&HZRBm~!_+zGi{kb4F_dHUIV8$$vb@ zqKlS8FC(K<(ez)YDCfKwTmhBlHw_S|`yTD-bvE~7s3@tfRes)L49AuNEy5TvhQ%GL z$QOkUb!}uMofQFTokmfB*yg$(yfD^AhoI;)a0i>mSOSBf9VZ4JZeJDc328kFpKK;i z%fpS@6NT3rjH-KmUodVMO=`b6BCB0IvexspLnVJEL1a(6vkefZyb#0DlnL?l8Ia+Jz|wsD?D$s8pRQ{Av>l zD7IkHM?p!_#wfs6UxYF5dNszcM6NRxGN%b2M9}D6<7k|gxFs5B zhru`9`WdqtAi2?K<_Hbog?Mb$eRwepA2$kZA5FKwv~uf6#j$NhD}PG7;EfFC#XjQe zpt-sRuBI0J>>P}0$Jm8b13u{@9I)ciC>NNPOB!{C#_OrIV;07JZKgzPD&V{gmA zevqkjw6WGPa&>Fw`<0Fd;gu0qVM=HSI>Uye&rp7x& zPDhpP<%R4FEabURRg*Bo=x|1QyB1?@Ii5z(h??QpEU8&9%;epv_{o4zmZpp!7*X3W zwIHautgUOKZNVd?2CmtafsqjmeaIQ8z2yg7W$fAukK=xf66oDO9irW1orY6U{Blt0 z>(o&n9$u?Y)7~&{axpDUT^klDb!lVmJ7s;i-guN9Yhip5YSY;moJH&b`*CfqdO;KH zQO2Jk#Q}zhJF~~E^M;jD*+t`V(ltpDp^io>!_>wU-t&FRJZtG{N@N|I0}pEV7pV`2 zu%lmUpdU9HiE}3}EnJ*-wqhp%dEk#7*JV;XeS*DbKydX3tlRfO_&h!yEcA;IN?hkW zY)a4_H+jdPmN*Q&*ObIKLwR9#v}_|mpfw-L^su8Io|;kRn*^W7Y2Pn4i6oI5o1yeZ zt4$2Ua|YbmIhI zVz}dR$?FBzHur+BvrQ;w&;KuG&;OT5{;u6(G-UYuyI?H@6IoQJZmKfnH`3YR;Q@$a7KS7Gm%GP_S6Q1%V(64WYFq=ec z>YfbI*Omr^y?7JGiKSo#Tq+%mmu`tpo5+nD#>NZqBe*St+2mh{6uEnblTSNJ=<2m#TG~$UQTJRqGI_)&uEkwe}rK)!JU<(grEj`ulFG*8YPv)%sAnJj^Ds$HT~& z&=FBeT|;B?4#&~^6mY*Yh$^~Kg{Gn}4P+7dbatZC__AS*^*a0{OW*RRn}!bX&(wI= zOal()mhK^_Nw{Ik|CMZW_pPACnJR%Q#o(svB1{u1@r+YpU_=4rq7K^O<&ri~+JE5~NK1Lzla?i|B3Dc|9VbzB$&oTw7Ld6En76(Wn7M`#yrp8+a2g4g z4pJg)O%udAf@acgi~R!wqiov-)E#A`riOXoDH=vimTgb*0L!2Q~hF1uf~&xYpd zjZo1TaKx>+#$%u02HaQDu;Ux|eu7W_42rh9o8T-T+H{jTI)upe9LOw59qqP`bEZFO zsQSyUKYFC$k@82e0MNY%9zZ5y^d8B*nAKkz(0i0Yi^c=$YzD7>Cpm!1`By#4Z1{wM z03JxcXyA74Kx2hZMADuSWA`+vf90#v>Z%TDKZ`-y&mspZz=uu~ZrI$(5owD*xNk9`y<}Sd6U?hiL~#+fQsAPw*u-Lu~HQlhv1KD+G>Schv29;Ee?9*!d8@ z;1&o)U%1Uu$sTH3o12wz9|o8$vqzl6%Do`fx0NLFtf9f50m zqmyhX!O5Ol8B8q%?#0)zZcB31-xlKt16XjY4Y%`IPB}aCg2{&M3JU(M{&;SALGe73 z$eGfpwU`Rtr8P^C0dtooV1^C2(H!$okb+Bht=Y~S;fSr{VZP)B=KcuVpl&MogFMW$ zb_>|yQp&ItFKr-aIV7mgEYG7X&!a4_v}2Y-q%8Lk`l`1rqAV}+%kp^pzhwD3X1Rf! z<(>T?%hy?!TiT^8f2xzSEIOWGC$pY46{Mo|DLC)Qu~TKE0C2%tr%?+kcm{bqc1$>k z@sBK=GQ^48dpt{j{kFWRfOPjhk+rOK;CPY2yMrn0j-g>| zmdzc6Aql)}&4f=@?dM(PF*F1E5wy6GFgY5OVTar8Zd+F1KHK?yY6mSQ_o=-*Xs`lR zi&A?2YiMl5aCKqkGR(DSWAVYHyoGv%Vh`_t5cxO;7VNO---g?q6LC*5bkl7Hv@!?0 zeS`dN%P*d1wF`p`(3?XO&XA>xCV7%*;lQ?FM`M_oTg77KBdM*e|6gPMgwD zop44yJ{57%3?7>qP2vxDUkj3_?ebD_N-CSD$Mb%iwfj?GdxjzWrnH6{$N$3`s#uR8 z9OvCR(fCwmyP&`bX-SI4qq$4-sjVL!tD7}R5i$5srCgpPqhY=RXvCx#frk|RZD{Hi zLtZ=HV;sC4AJY9QnWh|d`VX|)djeW_d0)zq(0IvB+|Qb)_sl=+3oE%9?0IxB^aDs0 z`Iy9E|GPa;Y9L4I zGOZbI!+{8lxwC=DEd*87>!~&(`)c#whw{%s{`rQ3_Y|VNkJ0F2HGVsrnaVK{rHI)k5e{)gf>ZK?P1|wlgpS9JD zzHZ2I+@KpHKi}7g_slWS&gcQxd3VE748vLW=KhM0gF&y~+8bXJ)F;M2X#a5%UL6oq zM~nHVr}gFcZ}AbhcwxnTWT6R_Xc27D@GIB>cCgYW{s{l2`<3~S$hmv7);n9|&Prq^ zTL3(+Ze4ctl#JaPtCIIept*lV&AW=4`r>3M1lor3Qr$~^cnR)&v$r%g7@4fOezym> zwBsMR2*%u<=#QMd%N~Kpk`xP!$5{N9M1An$Tk7C_<*`WJKbf5X!7!gY2d>7iy|nYW z=wZ0?-ZMRRJHO-)DR8x-lk}iMD%6#}&khWR*U(ub(0uSa$tv8zZyx_-+1<{?@F|2( z5q#FeXES`ZVPX#c$;LmK>|`6ab31%0;8P5rowW6tx0E=sSC3B&LRa%P%jw|?$=bZr zQ*s@vAHy5mAI0VXPE&`EV)1BV??bQQbZ#Enj9Y^ub>;exUz1<)^Eyz|(n56AhaN`B z`i@l3U|w9+&`*r{o1u+^ll3TGcI&ThHB&=DN$-=SX^Nz=K+Ql&E|7%pfiP~|jZ@vA zUc`FrLzQ(7vBoQ`ZGp;0R$gWuN335k)@$#mtQKOu`l`(O8&ETm)d;NKv7aj$jieh7 zDmQ)s)NH)*=ZAvG!F0D;^^sZUt^XjcMY%pgg{eJBVV7;H!u{zeCtr~>2dH%@=LM2e z;jN61r1N9S(EvZe1nm7-72qZT$%=sPKy5$)qa^`_Ou!HOP(V8-AoML&08avf6#-XX zG3%a30nH@=edFm7rF2vZ^Nf8INc`YK%+Q|4$Z19~K_uqWS7b5o0JRCl9PbV#7Xl4M zy7AONyzyD(#(bb&!W-X&8xwoZTxixgdV#*IWpo76AU7R6)SL9Z-AmE8n#A;1#PkH} z6%;cO#E9H6i_&-T?um4FE9GtzP}}hC?r=BcYU^Ii6_}T2A7!@60tgO&KslZ(r!7-+ z+l1sD+A7QY5~w#(-Y?xCw`-?`&}#7a_#=xCq94cbN5@C#N5NkFadr@T{orH|Lo?!L zPr^8xmFt}#EtKRw3DiE6y9^lY>_HgrUKbS!woQ@S#Xtd9FNNztpem3nUE%T=H}8qk z?XHBY-r-%SLO&_M>=`ef=AcuEoY&$H;^G#wt__L%5vXG*E=U97@jL=Cfk1RqKmah3h_`&LLMH4Tw2q0OAJ(Ved?vkWzP`8S*x})o{Lh!ox{f5~98kZb7_lpv zvD|!IVAX$@Pfavz2Zq@y=m{S?JK=$5vEXxfH-5roCP^|kDl%69^#{s)i)3<(Uu0+Q z3C2Fq?!1GpGDcpCCIRm_;OIoW=|I&W?+oIlEr5%AM6xY_S+wI!Uz~`qs-Q&?54{q( z0)~?+-jli*zE8n-A$*^M?;`m2!uNXkz6jr&;rlv#Z-Z~*%>lxIL^gX<*%uG9O3IXQ z?b|Hl+6hz_n8sC~e2^7455@gk2^4ocT=jm}5sTZ7Ues9{z1W0s`#FjLfq#SZ1l*H{ zyY5oHtK!8n-fbWb-gPTH_Z+3~#x0fJpmSu4L499EeIif~qxuX5s|j-YehJUJTr=jt3iV72W8R;Vi{pC<5hv@BFlQVf*tV)%}O??U*7 zW|Ue4-#y@aJ$xs@_h$I!;d>i=(;x*)s#{$(G>nr(zAc@msY=?T1QIAc+fhsyP6_XvP zB6TtOR$4WwlA894ngg3;HJ<~Of@;3)5=7jf<()4;ODbylDNfZ=-i@?)7n2rr+7@e- zoO_rlDSk*+GFefQ0aPDUGM6bS1SLICNnccwr%^J2DVbP6O3*7T)GApZDH*LO2~w2& z^#WKmR1(RQ6oHbm&q0X|l{C{Rxt%~t9-2=|(CsYJDv6Sm)O44XyzqjoWF=7jP{}); zNy&OpG60qIMFY~ zS;lG^v^v@p3&HI7-Ke`eMTO+{@!es z-?FUdH<{)43hV6)%{p~Zq#22K^R8M0+(J;^g<#A8Y7D|y*)d4T*h=!Hxs&MrXtMKO zH9;8_YTjA$S$2EJU#9el;h1;wdLrXZz>mmjsrRy%iE|e zRZcIUchSyNgbw|*i^j({mFr!Qz8NL1zmK91_d);m9wRyAcDGzHiES5$u}x z!+_*7I1zB8GifYd0_kf4(%)J_>9`lV0-MO}fbSyGq zLwZ6$dct%{$0nHVg!Co>>7$*L4#vZ{4fX#O7lRknz+! z%zr?78Neh#ExpMv=hNdIXHof=B9dBIqKw`>o%C3}i` zu&7}C0JV4mZrMayq({h&PvR|818y12Zegb@8K1+Z*f-#oMbs3fBfyNipnrK|18#Z6 zLAPvUw~Wh%TY>^^v1QXO+u1Fj&V*ae-s6AEYcuJVopcMm1l0~EbLTy($+Uk6{mw`B z{~p}ydX^{SOY99B;Qi^|Lgjv3t`@i|*2CXei#>cn7q)M`Nvvwde)|A~UPYmu?@`TI z9mq=`xOScAO%4ybc#Q~q=9RGX2FYHpyKeXZnt zlWi09#9 z@^7}KDf}DHDLMu`C+S$bLe?>a>BwIp={SJ3{6)2-<8ue;n7o2?n1J0oLDG>;`CQC& zq^dgFz9Q+UR`PM7CFwXN=i}iVkTXiw(IJO)>}L7+X)C1NIm2}1kdCmnN*tt$CQ>5j zxu4a@*KJha?Z$Q}<^}_yn8&w_R6NJyc(vg`Hr8f)Q(CHRY8xgQrKB8hhn8#}md-D^ zRgClW73*({qCCh?!<)@0J*cHR$fx4f=z;Pe|7!~h_4J)hp`OjIC<^uDbnqX{78jXy zHX34P0o4Y$t5>(7mUZuR*#)`sEH>JG9pz!kuJAktR6FDu+eU3-@4N>%ViDa$24T-E zg0S`t7Hzs3=MUlbDzk1NiMs$)XB5|nL0B_KhA@*LBr6aq6`oIldH{L8iBTamM+or< z;aqd_`HrrpSksmrvTMN}LIg>RfPWZCnh8`QN-Bz>c4VAI;ifwd^6OTurf^eWq$&PY zf0r&^5!V(d3yQOnIBw1|*}?r5`mFc2AN|8#*H)T!8N^r(R4OtWVkDOcP(;%dU=W^% zV#=ROu_kWleHd$+D#e;|SfGi;i%v~4smp1ebH2x^ZJcrH=^@b;{xuM=@9}3o+fV)nx-tgo+H`beA&8}XnWAV0h zO`Ix6jZ=Z~(gdouX#!Q7YX_?E_)sN)6|V_w^-u#|=}8#0k|JU0JpRa(ex&1%3@MzI zLE)^_?cRxIIT|(6EQPAl%~J5HuUQIbJ!X~yT;0u5$g7iC3WBwh__MstBr4NulBf)? zp53xPHtZae$3Ud7> z0<{I}H>0H*B;ZlcR@5^tiUC3uy_t%wPuwj?FjCT!sOb5Axvb|XP_Lk#i!IcFrs@sQ z^D648;Z!}}kR!}ZQvxe6+-TKYj%8|&wUgE4C~Bqx^%|;iw@@N0qfpE1sHI4w#lf^3 z8n48N`~oW;Ny$@+k|v50Jy6?FNo)%xM)b=zQ1S*U>8Md+VoFZURia&fF`_t0i9u2F zu3J{J8K^f=$(PNQXje8Wd5h}5sjB2na-6vXbCtNAU$m>N9aDnyL8io}D6s(bHYyp_ zT#4J=dL5L!gGwIPC`n~XYQ`%ONWZwvUo$0^+8C9*k4n~Qlq_IMKAJ5g zU3oZE-y6R(!`MmoT``O;Wi44QvW%@Uc3Hk;)7ofSQ)g<)2z3Asqk*)`KQDMHs+hcBZf@0}m= zlaT6@Ja*@lzoj${+(8?9qhj_AP;+nIz5fv`V(bpz`klKGD(Vrq$9c8uK9@YLtZLU2M(O9MSu{}66sbDP%azwu5%;HJJ-259i>aB!)(U;D zUk;CS&SRAk5hn=b=uXxze?RTcZc1N41pY5HDWi0pBD z;XZm`<=Jb#Uomkz-HGY{6t3r67FI^i2x&Z|zxlso#<9Q~oBgl5uEP4iNwoSif&1`_ z5O|Y8J@58(dtS78YvI|-^Y|Ec%m|XYg)V+3Wuh^K3HA(1D~ezBvCd6hd?a$fHia?z zX$nP4(h{0)^t4n;{b2A;9*eZv(IlO^zUO!)8P=JQ+;DavL+rc3WV`MsQK#o9t;)bT zzm-Sb!;6yU$F~~H`ET)}J+x9|EC=}Q_f>e)<;2<=jR~s%@%*toFZject*7v|=mE#v zsPy$N0sVfX#qveLlOKd%xwIJBw}Ex60{2;a|4LSTvUH@L~4hY}isvY#V%7I01-a**>2LV)EyeUnl! zxJo~EI@hZNp_RMsZnHgv*bo@}B5U~=F@l@KzYNSixELZld1vWBr?q;Cz~n=TxX#^w z*YoAAD(kR!haN=N*L=nUJ)PC@X zdiWX0~X1df@Bih1YvF_W&E(xW_S@*iVl&W}@E&_Z> z&Mp}xPCK-WWX|YRkjjaTgzf)9MXtj~aL(HG~ z+MK}swK4{`7_>4i|IP{~&l04RjODn|!Ui;IPT%s1QrF&^M$6Yvnm_iMF}hZTYul2C zzlb$6j^#2T8@4<`pJrs-l#bmMkSIcT)GR5Q1vf8Bpw6*K4NXdyA1ABv&ZuYTMEjFO zQFDz93$HWbmrI+f_{p*D|1vOD(SNl;QxoR;FDwRZ$lsaw*zf89P1s{9PntJi{?dt0 znrsUt0@DJ}x)yo*Bf>I23 ztc(sHftLC^dB*ZoFOeGqT+ho-V-slCNL22IAU?m5W{LYdwu}BGz+YT{)&FP1 z7QEOaVsiKXtjmy*!ERpz`l=A=iK!zewhQ|`N!nsOL^Sl?k5VU1)4oJDy8g$3*`m#Ann1QTW_|(GNyBle|96ABsseE`%<>ljBgU`(@eR&_fd+#< zB4oTG^Q;j=wev;%sk9RyTh=qIAj7EFh>tOgXOGT!RMv01GdMLa6|Z4D;R$mR-%CVZk`7Uzvl^VFjMAU zvO-?XInQ5jBIZ3jyUQ!!b#M5AB@k3KV8IUf!vBQ5ejSOGgl-dr*(=3a3Jx02d5>nJ zYtA96qY3Q3J#dERQhfPa!@)JtC!qpZRQqmJEh`!U%RC8HonRmfAcz~f3eOi?VR*Nb z#j5&)s?wn+u}s*K^UnZRti_=6KW+;Bnmq90u?0ic08wm0dMF-Ak?n;+UzpJ;u!vi{ zun1RYzHQx;09J|+ajo2Giw~oNq+*B>qac2{BOS(p0=G)=sqc&i9ry-A!Cd3PUJiK( z&9Ke^-&K4J)Lg+A9DV^fm>H_mNt_H$zdxe~Sg9A$8yMt+E&}X#-Rt(U6kA|JX7D9z zfFQO}GC-C?az+?fSAfsA17(68gO(bn!ThIi;Ghp#7=5PgmAP#3kJvgq?jltRrKaH^jef+}~6;!7A2^5n~)4uYcw z#l`Ftp+f7%XZP^ln)X1;u?4T9ue++)cNsMwkytpq?jF8L2r6dBHVLp({-wZyr8NCw zW&Eq-gu3J8h7>YneN-n5i8kSBku$=LzEHJ02o5;>9#9#1$1S(?3gdra~LtFPvpa;)P&ycr!t2DaeEG6pc}7-jGW1DXh1`_Kl2 zaxttkV4|2k&zbs*ViDH!0MeIyrFfhaX#L)p5zBO?LEXk%hl?ud8CU=dp&;-`DaaJ* zyNQLbx0xYlGk0NhfJ*OiRhpU3YeB{@sJ-`CD2uTwkT*m75EfRQ=U62F$^$Q)^ve96 z9Vo_4Im=M}CkVbic??dS2t<-GlF*oRja<9`OW8dx>LYXw4?@kK$WFel0T(WWmzcaN zs`45{&>4NuTwflVQUEBn3?ZxhFyUrEJ%;WEVR2ygfZvm*KWIUiXYkiBi_(1nD9yNMD3b{bfq`-HvQ9js0X{D8Fkd!ANXIb%9Zz!0yF6$|pytOq z-^VSefQIFvra-^H0g0=$F@37LtaR@vk^{$ z$tkd@{SbjlKN#YMV$Cw6ba-G5ZtgHOqT`C4WdlNrgobHFm+g&k=CRHuo9?79dz+{(JdPPlWF;p%4#~gj1!`bU2r$pKs>9V1VZsd$V8_>C_^_uPhxcBoN~?2` zgDYw8SSV)-v*(y8Zz5PBqh>AB%a+KJ<4#4sfKn6^STX^f*s)UpJS1Ef5u&OFV3`OJ z0Xa_D|4GtqzfnnJr%ZO*MznroZZCoFsjb+cZ*M9_vjGLIi-87^Yofm_>QHWf|k0OYxk+?Yjj{&J5IW z&(Ry^jO#cUu*(CBx|0bQodGvU{~3;h;7wC`u1dw<^n9%|riZQWMaW+7ODQNbf-ybL zF@?4CY|E?q*jG#H_~kzc=qWQg3nu@X2db(#4W7S?01oBBIb_aOPSGzV#$@x^~|6W*p!;cM+eAW2xa{P$QDY*eZJ- zQGFo`^u`(F?4_sdzLRPUYOajbWXC8cm4#cNy7AyK4ye}!Kp%%Os)9U15MTd=)%Oqp zdRey9!$#eu8}A>B8-M?K5qnVxdlsg}Qvk?I4_dR9ijnQ<`_bZA$&BziCRq1=5+Jr< zf)`HqHZ(DngvzDWc|8M97OP*(3Eg^5&0?n?*m>kXZ!A# zP(I3j;Wj7&*Shrta1w&LN*!6c`x$|VC!`*vlLKa68aj?Gxtcs$aRC`#SK7&5VWMhFR}XowhNOniN>EaolkqzU002ycVa#c7vv@d z4x43i$bvWIp$ZYH|F0;$YTunw=fiR_P(@%-Hy#2t-guQtA!r>0lT7C&(>Js*bcE`e z|@qh!z)Ty#~HZ#aVO0IGTxsb23a%D#w9V#hPlJn z)Bi+=urR1)k#zBurv4(3Oab*Hd-{yE5M@x7JKd%T@Hcw`dU>*9| zj*nsz4*2rJb7EqF+&M!$F3$@ujD%OF+e)>wY7!3eeFv4m;w2%d5!r>$vM>e>Z9%Oo zIvUpAqX?>j=fOcDE13n}X$TI)9EYOWDd{tU5MvAz5K+ZF9Fx9r784~*_Gv$KKLG}2 zKA)VBX0VMgfvMedEMpCTW3PFN(K##N1mK*CR}q9^`Ra2bQQkcWC7gcdf{36gK6Z`5?jL-etR zQqMd)pvR0AL{yWNMY_Z}8PuMTHUTG;^hPwKdt$E_&?!sy3c7d!43AO3YaL%$vUsuq z$JVE3egk%I>H;Gv_#73m#}lsRR8l!~jRDxLgV9|#@bv;6I4YI2Y-Y+%f)}Vn|8t2I zN@><|y)4E=b@S|QB*D!L!M$VYT_*VGWlOwP?-hJ42+B#TvwC(*7bBt6^aCIKVkhOz z9XMS01)NoOP<7JF{%1f0uM@3~43CHu2ejhxr)XCAc)CPX^1`oGFaf)-b?L3!VQ@3> z(D+4~5nQc4fqk}hl6m%%B_o#e&8Cf`1S9=shFl0n1z@>GV45meNWar~UmmeOR0Dv4 zn0da(`EMT{F?P^V;CwP4Ug3vDTwq+kbhdIC=Qs{85q3&*!@?}PVPK;aRN@a`mqe^b zA_J&Aa9Ej4F^~SBHbIM>+(?IwB=`^6j<@nY329zKFSI zR_>y5R|~<1xloufW0pCp;l_} zblk?SU{%k9S6jY-jw%zNZUB}ELLb~1W*=eLDUl8cAh*n9Q^Hf}MDZoO$_r}ZKZe9Qb<$&gOxjpLC6SyOt$R z1Tnx_sQVQN$9aMQttTLiJY;(1AiVl(1f0CtzPlwM579M}GQ4g>0Vezfuq5!iYdgS! zD1Kvl`#ORcI(C?_^8qo(pgZ;K=*K{m-3!3k;FTfope-pqGzUph-~_0AFiS5y=ejhQ zs!3098Y@7}AdSVdR6QlR0rRw3t*%e#9B;NDJOH~Jbho~q@mBV)<>lqS6Q@qp*3*9y zel4G1$ojSXXRvr=PCRGjuv9vp31=;ihwBF+&Ukd!^20&h#8i-8>mr? z^%>KZk5n)NW-AKbY7!*B23&8ZQFAS!oyC#34+h_BgpBxpY3-sAxysjII@jRPQa&H{ zX4bTQe>Jb&-D&~{*)9lP!VV?EtIN@dvI@pLgJ;`-NofL-oG^uA)^LzKqvsn3J zUEADPTmY10E$Lc9`X*PxHaB*iBjl9;Yyd&YD`F>_;tfjJuqYm1px$1l!fDZ&STKD( z5Cx`w;HUg<1#)&VFg_1IQPc)RMv;U9!D4{Q3JNj!x^N?d->-N@j*Ec3QvbHcKMk#B zxoTpmLgfx_r?Gnkery;rahr_Ss0<+!HX*VCsmk3Kx~#)O7QuVRsZg?;*sDotfR|eA z{F?xowUYGowwT1-FEu0}%6gL0WDLx|q_4xThenz2J}8Jyj~Ky=Z%RTmfY%5=!b3Od@rD^%Q9s?wMg5A~_zwAKZpZ_-qIDamW>L{&yL^qqC{)Y3jSc996ptA7aL)sQA z8DY+*;HQzuukYN2T(1(@6;^*<{&bIZLsQXZiEs2WTvv^F5PSv z>ND4ykB0ceEX}QtrP1Kk(%FpFCF1zqMmuoMakua-D%6^n-S4UhmUfEnEy*C*ybKsn zf+=!a>jdhynaa&O6mpd5W{*y+(&P{vb`DMk$cRm2SoeQ^mr!LF`@~EWvTr~_bzV?;w1O2^V#kW^uy zo-TpUgUwDMkZS_aIL99%N4OslxX&^4{7+4SYb16(tBr<5nmrzX@6u<yW)3*Fiz^t>pB4Af*LYj`Ug8UDH2$=t%Yp2pMokF@&%SdO~y z!yCtpSbhl$pIoTC!;fV_CKknVR)v%hQc3MBb0fz%s>=}cK>K#>yWdxm3mSA{~OSuIp?*j@=})I0qvmM9T5}mDNuPZz1$~l7~b9w7hiGW%}9$ zrRpa;vH#$AQU!}`o-0qrkWg5PzBR0N`ZyF3rvzctVN?r5i|5=u|E%@nMsf7_YE2QY zR}<*o-VI5JooB_?#KYA#-@~dG&Zp7-Y^1op$x=oPq0$7=@8=Em0s!=O9DFye@X*Zp zSwV36+cz*TCTuue_&|@|3!=l%%^BwB3xRXrH_Yv{tTDIui^y6o9&A_xb&thn&`6z< z6qGKG?YHC|xW7JP$e~uOM0eNhd?2z@0Tel4Ag8dX-E;jxpr0F8cG23SmNUE^#Ml*m zv=IYETv*94^D4BTN@9+I^9XXilH(|miGWgX2MAln9M+shppVzP6>ZF&45NcEvi4)d zCvbJ(tZhat!530>KapERg7Jpl5BY)MuoVJxf$9x@L<zTh$m`juYnEEmOWhJaEAcgKLGdb|f&C}`GkzGS?i;$E_ zW$nX9*(Nw~nw&6l=--x5dtp|;kl^eORmSSX!h_6eDI+P#++cTv5fjA}u>9Bo234t2 z@caDvr097HH{ zSw~+z1?PQcnP+p0XtO!<5Jt>(m6G4lO08UoOte30cuK_yY&h7V-41}b@+EbON%eBh zjB^L|!e|}5Jz{n&6e%wceqOd39bj;7FCT4R(5Zh5`LdjfK0x7*};|1NKg>kR%%<-)zhkEv=Mbac!D^sfv0ru#q7 zVJF%nTo0M&f>F7cz7wWf<9*=s?jum|m7l>Lhnf9wz?VD*X>UsP!Uw#ECU|py(+StC zG;X4=6UY%S!JEBS%Oe=SoE0TK2?PsaA>!Z@244gIa9vxR*2ZBQl9I%wedzEvG}(K| zlGk-|m8OBnGz)}Hc}j9|R6i;Fl36Waq$!yjc?qEu5Kj+r&dto&k6zwTJe_Z=Sg3f{ zs`MfKFiYb;DRCO3Z%K}gaybJeevua&ob^#D2xEw5jaAFxXoo?vK%zXj#E34BeE0XK z36k<;iw=$<*)R@h7L`VOCy34wE^ZK-wSEB4Y3}znbG}zFl)hPC7@MQna(DvRXlMd8 zRnJiPx0r7CSwQ=X!nq&%WoK6N2iM%g_XVI1e^-s#llT%pD1Swg~$WxuPNWU*ln%w;#S9?Xf6)OZgC|41AR<4Njy2!vK{j%HWBp>?`98Bp0}}*dCZgK z{Wue~Pn3Be%s3(P`Xz@z*vONN*!VvLUEa-4fLVKauP>ustZR@BFH?EUwbPVPbqW7w za9E9!`!4+&w%>@i<=%QT2ZvhinWH@3Mj_4Ud3=)%E5_r?TR(`ZxVPvWTR^(LBoE5= zJApHu2;a|AjJ6qt7@``#QOAjH4@BJ%^Iif#yo7)0^B;s*zSQ<`4@pVT)IQ99z9C<~ z{1PqYvf}Oo36^m2c|wB1zFg+A=sC$m6~ns4I;1>1qzE`iWW;JSkZodMogVNS&!G>0 zGG!R)L)%nb03U3(e3z0F3%cb_+KoJ|~1;6@)p5 z^#XPKIc+g(C0*sJa`LdDgCRk5#1*#ogjg3Y`rX@EZyoyMc{A^Zd{^gKFM}P?yzgm* zj+t!!kNTq5H!Pv!i^6FiS}OxQ6H7j+R{=`a0efe>V7Bz6zdYBC%UV}7vO5uV(+)Af zgjEL`K7IlR7-lW-07rcQ5E$JusTaOT#_9 zL6Od9hNv3_o@lskgg6JIa)SE$>_kiaHR>_wr3^(nEYR5P9Bys z%;lG}r*1~7AD~-67Bz}>mX$+)A1_C2$blC}NZF|)Z;gf{6HkDc(cXl_$6)UF8g%&5 zl_62ylu3FXuaUc=1LS~|52g6vN~$47kH6@QNPn{iqz&XSLl@v`r_~Xzfl5l%hgqXD!G#$%8iy}o)on*{s__`o=MDU|N;`>&vQupxM*$&oZ{fmU`~A(F2?dtvZ>wN- zOcZaRh7bBCH+6GQtKYyqNDvhIzH!Og8TTgOtWo4`dM59$DnX~%ZCwxzcR1%xphgY) zbWMX80-)}i*Yt*mdQYe40|TXU;F1O8v^bLY;o6%3!$d2(OjKN`T;!)TOaLPx<}mdu zei|rC{XRLd7SPiEvAhEn*4H$*(pT*&HwmqIk6A<+ER$(Kn zAW+}HnLPbE5QC7bgU_Y136sq&j|gP*8#@uipJLDAmk)q%cn<~t8Hjgf!-k}_#lUvS zyOfVt0M^=KJZq&|JmFA&ap5-LCoF=+avTx8KTP7NMS^$u-HCw{YDs4 z3Jb-pXVBY~r{DOHW5WxIsi$+9yT&QbxhCow&cB9Pl2hqSop`nbRHAeJd00-^0HC!? zq$hD=Mm$|qpS-=Vr@T-}!Ms?DVP1CP-Pb~OmqlgdKIxQ1FvHqL@&^#aWZkM4y z(@g7W!Fsy5c4@3Oj0gjnQdO}kFjjM=Nvs`@@9BDbsfx#b^dO^UU4F1&MbwLQQ^%&B zh1=wG@N=G(+6m6J*G!W ztG7IscOZTt^_vQ3@eWoL$@=~kUZ$!#jxbn2K1D)U1wh=HM@Ao{sZavlk)19V@gFRp zyNkm2K78n3-OleF>Ju&ZV<0EYQw`Yway)YPfVtm%M{({z#{Ynw*QE*-6&@P(V})cr7y5$Q*;GdA{n%h%D3a^MD2Zf_ z7G|C+ekAOB!{{ibI=-+I-SBsQeoKpxp$O8Yt(*R$^TPsb?ZbRe=(R6DmR>CJyV`=8 zPB|7B4;y-wd`ANs=R2Z-a7N#kHhDI(+>nhvl2XK^eK@lkx_^17f!Ff)D((Iqw`mU; zpEu+v>?>i8Acim-eUo&(VSrGFQ9=MEy=E|jaW*@0!B3ZmtX{vH;jp}}eP~mSr>B(v zt83*8YcOBzYj|gKzr=f`IR$!!EIf`3fwbvtjm`r$%-TT_Ru)R)KO|*fkFH@HM8a|{ z`-H`5X{y-ToM`DQ&|fxIUrzJ$@_rtPgZf$%wMa8Kv;hc5r^gx`Z=dnfVMKpqu}yU4 zNgomo+aUQe9aX9pQQIn~l<~s;5LvMyw~LThRGSo*9;8q-GcL5vz|GwMhnEL)5wQ(_>7vz^686 zhdO!BQCE6|LAl|LD62X{?_c!NOOj6ZsT*%j$t<3)$jSV48=1nP~WZQdkw$QJ35ER|9j5MPuchhI7hj`2E6?uP%S=DwWd-XkdzAc0vd-yCo)0yGfb-Qr?DUom!{)Hv6qM?+R&-9~+vjz%Yk<7~c>r+^gQB7AU_ zS|uU+rTSE)>7x<+JTQyE6PPJbZDouv1O#da0;Bca^ubBTX9&f6@Mo2F8i6wPhZTB5 zX+C@uT-GM8UxW7`K4juDNA(Ndqw%=O4au4&m7k4Dx5}k5lzc7kXjiZ{rk5~@_7ang zK(~X`2K5=>Ul=>_T21;Xs=+uzfHcnK2W*%-Z&o9ZcL*&JX^GOviKpAlw_vejK*zm{eGn0@>~wq(?eWHKgq`%rg}L%cB<0(}QAMB?=4x&oTI=?+7Ywy6>S+QSt>mFl zMKKb|Rj!0(F8M&XT>`8D)S_e&QOR=S?`{Q2oBF`%wa@yW4ZLfVoIm@}75Ms@SVlEv z`W$`$bU~4$*Aug1L*?x^)qpsRj%*f(Q_b*qJ6S2o9!Sb+z)=z}ex*|YRlHnetAlp% z_cgvil`_i1>MLq4BJanSsAVv6Gas1g!Z5#35z27Wk!bUWSGOd^_OzyV z9j0~HFP&@aumEzREMS?Ji)}D3tkt;s7d4;Bi&-o8irc zjr%lAJx1`IaNqOzE6i9Q*zO;?#QmEN;|+O89~qrn%WcqFpC)OikgPZ*-n3YQG&EGK z7+6ROMQbt4#nh`&65&VAioGN_d(9R(^7~`wl^if`Se^f zK+ZxY)-um!R%fj<7|lpVy|F=tpWmSvyAv+hFvqDB7nzPlT^e;9aP zL?w!^(kB%-Lx(jzAQ$0T{nMmN#1J8)6X|)w&tp?}kLn4lpes-^+*YM-{W(&GayBf` z)QxGlhSf+pe1H5ic#~=>_KXuW=aiF-$$E+HujOrsS8L=zqwr%udm4UIX_^Kcdgkl@ z{%k*ldjz|bTQIBo%Iz9c+|~orkDLw$Dz6(7`>NOuaAR&-xplD%|5_Vq+6+;KV{XC^ z?X}89TF2enrWz-g3R6Dropq^CZp?n+UN`P0Y-J-eq&T^v`N1ilbwT3ShWg>h2dmpl zg*CH>3nc;j4Ud^|F&I!i{W1;sqtN6(yr$I!Bz=Q zV$3b#C)U)}0myY-=AT|0HfXFn=iR3b?E0L-OKsU02yI+q$6LcRz27jP}mtmf85EIk_< z*0n~jyJRO5rTT_j4mSH!XPHG(h+03nU%ksVHgbRbAL&C{%cXAoTE<>llC`W6O757K_{YjXq!6MM?8({z<^YQO;J@9`aTnZ!GD+2;ZiCOm;{b7sEL zX1Rm$hapbfxTM^Im>?~g=ydnI3(gFhdnqF4M4D?g?__-TV%fM;{G5pm z3Z?EmG;aD3Rd(J1Hxl&%;iZA|kDf#*WfBqYXjDl?`tv=>lDzV(S zQwAd^CpUUS-ChGH+%e#2jq9AS&+9YokbAc#Oz7unIF*>ue<8vZfq9t7bFA`b?L{_o zHVg~C>#|f~er-rP@qMi+zo6WUKPTr-$bD{X_rhmXZe=Gc69`O9B>^PqYy?L(QQbFN z+qg5tFME}6ob83JsMJ(xa=i^|O7v8XL)W3zuw3?t3+7z%`?YgYUaq!J8coeSoL|1T z7m=HaY2Ti>mhC`P=`|2*;-09|57eol=V=@aOi4qX!PIlEMg+25nNoRyTJSBJWjHs^j z+O1BM5a*mm}VQca&PFZ7Gxj%#t<#pOPUe(wvQYa_D z)NERSSlMri**2hee>L1tPAZua@2rn$H~#i4T8Klzm0iyQAGyh$P1=dZe8oDvERc%-~u{;WKXF=Ms0ZKo4OA`WBboE)la zY<5-XC;)8i`Vup=T%1un2)@)4qL1mgduc~Ts)der0-=&ojwgk<% z>?c@#gGd$`3Q#=h_HT=9b)0U8*Eon1pSWuoNP6~&?QI((+6zy*^GWBwtc*2q8TJK*f*WnA;F=0&R|ykoY;WloHh`zT z7ZY%-Gw7!o1k0X27tBKr0cEMFdY2S))D)mKUG=^-%2{jL8S_46ZjeQ4>b}Lhw`PeF z^3MJw5+QN3?gwM7VKR~FZlo+@oN6gVfM>+D(I z#ec3ERoWeTDyMoq`;Vp#?rFrzXFi&A+>#ODP0wxJ!$0!9i|!1#R^X50(YfQqpk#j(_4P? z7q@N>UwEYZB<+P_8S29wF<1}sw!xT|{)PK7ke=I9kv|gcvLw#mUR__5<4@h4Ieq^9 zMrE`9LgBwkH-|Yx>o9+J8ong*IR`jY5FPqc`Q(IA&kpB8chPV2!kfQ0zD0d|PD>ge z{r>mMzq#4PTR8;=f3|{E^V~1rHdV9Pbxdm9{l1DFXmiGT!+-81$UM4sSHOL%`@v2R zr>?P{t}$0=vOO;IEeLYPysi486m-^w{$AlTh|DfJc-VKlhH%Wy z>DT+0ZIel&6@fkR<5>aHD@K98S@V{c{rZ_?FVadYIBwD$4R@WnwtsoGP8S=-gb!2e z6nQ3(Wn8CkPC;ao#-lAP8dH9@M?8%Zds`l1&-q76Ppok4*5DLMZ?RA!wJzH7%bC69 zmVddY)iv_0u0)opB5Pj24-us zTBI^-xr}m88d1UU4P%%hG3yaEq5YSD$8ny;2z%oVeTcHaTT( z?!31%ub9PX_8cv;X+kqEYx|7R!HHCV4|&*2*nex!rghc1d$yCN>a0a)-ZuF&3N>eM zN!X~t?U}sKZ{NJ^CU{;XDr3x?zc+iH7Wru+%$-wj3XTTw5X7iwy?l2jFL z;1Xr@UCf%ZZ->rXtm{9l_rGz6zcKO3LkXN3+XVV#-l>U5=V#_gL05nG#$?_WGDG0*=e^qrLI?Y+0XXyFpXc+`%V6FACj+9vz^Zz z&rolI)laQwXK8Zt&FU_`&uVM0@>!J*NnZ`kFIak=C~c(Os-per+)}Rm$%N_Jw@!|2P$A>;uwF$@H75EA?Pa6G9<(m6h&~m<}_C(S@`5O_5 zVTLYVGQBo0N5e|@5ve9;nfxxx^B+F&WO28yW@_424Xuy4)3h|!@~Dwk-17YC_MV>E zhq!`1|kjCaZrh`8cvqD@s;Eb!HU;o}h|KH;QP4h;l{R(L|>&G0|B)^6~_5Pd*@2Shu zAl6%QsDC@@#Et5*>how{zEI$wXR3b4@^`0|bUQWbcHz4{c2TEa%R9^QsHYb>yQYPV zp1*!_s(0@SU9@*w#K~_{6_|1sPFgL;aCT`=vXwB~lzO zXHlv5XZH8pS+0Y3w8uGqP1A(uR;I^!Mh`ew*H&Lmy9-K=E2R1a9X?pNefa&;=>AH@ z&3|n(VpBb1+oo4Sr($wwQcC;Z&e#hY<;SY0=wWYY+>z?eulr2%J-lc3_DMlSy86lC z!xdcj%iG&`g05)S-09dNJe7TKC)8a({ZHM5cfe|M}D{qnw-pCFtgMz0=^VX2RwQQr8ebaO%Yodw(22)wmFjKh-()eGFUb@ z^fy^z%D8YA+W}F>e45-B5N!;5yT88NuF<`%JN8z$_WWVRxl5Go#z7{gwjV?EBZz+t zGIh1mf2kgQnmqbMTjlBz8jFbftNp@RV%sEZ#i;rBhQ6P$Is6&pnF8WNnT2}s6lTa;Q?T#lpR_j|O)Cn-CRJ6Lws< zm#)6}PVe}B7{T259E~^g>Y|$j!#9i3jX=?hzf!x?y$tRp^2o~&c5dKru1hU5eplxG zeMilt`-4eRxQ#|d#a9Ju+tjb_wl|g8Pq!UTr&#`&bsueA_5JITRpgUjwWTqyd&f-b znc)VkzY`W6{tIxA-aV_b3UlGLYlFG{qmJEt?zqDuvzc+>ESrBeQ|YjmX8l!Epm&&) zl{KRF)o&D%|FuHvNS!6e(API_<3OXK$8Ulo>EVs@d@}2-6gYbECe$xT4q0~$=;wU5 z<}q+5x}ucg7*KNY-_F&mKerTh1wMPI3dzaD1gG)3>b%M*&?wb@db`#n5#d-NRz4*z zrxCYmYW?$`N#gwW`YXG0KmNhLGMB%apwU*pv}I(9T;?{q*T*aizbzUSbI~~Md`&Ww z(oLuD<}5$3YP?MkVVpI24@HyABvSijlYiR;XUoVGZbsDQA8Q{F{C!o`GNQOZzG>RZ zOFX53`*g4Pe|x&N7K`ce;cw5xO=|#!L20a>9(IvOMZO+>R;n zcJ02)$%QMI7P1H)%Pm5=f|{A&3MTUz_0fo>rY~aW*iy&Y^AZIoj2_*1aGB>lN0QE) z!IG`$0)=Zi#WJ2XjyaYX8>|{%f2Bs-4Oy!EDYs!uhBlf*K5B#+w(HofxM0yX1@sE>>a6SO6>MiCh^+u z+YPPnl_Tyfx2lHD^qA-_@900r_3gE;agm>)o6e>740VyYdp2!st9(3q$({dx_(-&g z-F_)pl&vK6;`c(vEPIE^U$Ue2@w7;jykIX5ai`3KonnR3eM<-XrMC?#LNBsq)jBV| zC2qy=fIh}^X4Yt|ck;lL<;25xs(ZOK>#-3z+}L(U$%m4~C)VNgjwkoh99`W$lea5T zoeH|ox+0q+cXHoP1c$rbDqF*ibS~`MWe*(;HcsW5{ztrZhXu>uUv>rl;#b~3e)Rb^ z=!4ed&T!?XB8pGfa2~6HR%)RV=5$_RjodEh+@~{jiS~JqIbS5gzDyKv`Zp;s`K&SZ zyK#8#{1;F?_NcJWpJ{$YXZ5%4oods3+&WuA(r;Z+8KLLav)3DMoK@C+<|Fl9AbS0` zvH9jF|LVUhb$x3weJS?aFNb|OTpmq|WHOziZ@U9tcG5REP-O&ZCS8Zh(Y_(W>ZPyx zMnWAQ-|H`WeVWhc#tB6|=b>Xv>dirSS7;mqmRV1Rn0uDeMqQKp7A@^nusMnY(gU8C zleTxpnUZ+&r=&mqzLwJO3Xa3Si;lov6C%sX^Llmu4U|s@_qhYIdQ!U zdGm$kA7#v00n)UwNYt2rb%uNB3)g+J$kKdG%$`$x?Q-WRn+An+ImdnFj%d>3X%3?u z@X>XWc=U(DuTKR!zS+B^0&=I`o?0G(M;{h0W?_jEVMWHX6D8r;8;sbW9r`ZMX4saw zA4%P~F}dv6Qnp)=6iL$;*xH$IY_UuH7j$Flu?*_WtIIjJc{3UQN78kNv;BSlqEzj+ zMs2MRB}T2#QhT&^B8k1#id7{PMN6qhLu*qrhC;QZ7W;PD!2GtY&_M7Fqrlc1tD7gI&li@PGZq5TMm=L-d!CsI z?Y?yF!0|J;@1@<;c*m$B+5Y=36KE`}wjHn?V98$;p89;JUvdBQKA6P5ypi@*b0JOr=J)OQTmD1Snb}+K%yB#YDd6-s zbq1x^%d#I1-8nto=1;7X*Jstb?V6=4@A5r?@;Q(X7dnh zH79J-SP1q4QHsm;HW!%ncwNTQ6%4Whf?T3^8{MN*u$DFK!XQs!;j^jJ@4@Q@#VL(n^pTX88|U#mW0UjqZKAJs@glC=f6FOjVD9b4g*x})dV&)HF8(R zANbE|Ts$y(7bMi0!WTVdHIdVNXf*#|ZK)P~9S1!qxP5%GX{C|$>*d3I?1omDik_3f zO@ly8di7bdB)Y7vy+$5r-(A0L8@5-Bzj23$?c;LX33e;IsGa_#wH+fXTfGq^d$Z?a zMSHh!aG2-P>XxkiadSJ|BTSNa`N&xKy(2HR%CYx*S5^B}3&-rXv*LCZx6*bzvAuBa zw|(iI6^PRL{O1gmtQL@}s&Ic)pHR%tqVJM_wXy4)GLi2KbNsnY~^AcD#kr?E#JXTEI2?x298S}k(D zJ>Hkc|5@>b`^=+8z$9`-z>UZ$uY-{*-Z;6!fx4fzBC~%)&+*a?FFx$~SZKd>ZD&5T~`mCYw`I*x< zD4HgB&GGQ1&pghpBI}yc8=dEv&;Oyf{>`uA&ZvBUZ_saN=Y#k0fv~Bi#_&osXP!2V z%Hg)0yr?zn75e8VYMVBLGRtdIX@4C2hkB8VK_xeC=r{jV7n9o@bcO!R2s3ZFgKCMd z3grpn-OhJ4S*wufkRUP+<(`n_YW-UN+K$eU}^3kN3xLj&(!-r>E7WmPKKPY$lXSz$XB031CAqQeq9Cijb6nr zMf8S*r8%gg^Lq#nogD0A%;CAreF*YcpI`JqAK2_&pL&NsSEd*hI5)FpH@YUzxI^Vr z0&Qb&Z2vV6m33mLez^}H{~j&u*cA{iPTA)_cv(JR!W~i^EX?g1Flo;Ce(xhV?e9JC zu>q?aaqaX2)RN!Z>^PdT|5dkS(3Zv0G$Wt=d1UUpgM6&!t=s_Ng_ESH!XEuE4>YKH zdRiUnJN{zNp)O~=!dS&7TW;Y#m)VKhMY}I9a1BikiAY8fsp5uWiEx-s_LN=q(vd5o zUa9)y{HTp>!g(9(oOJBpSJUfN6^zm5i2q8Kh{#|PCL=&Z#dh+`+2(aX!J1EoB0}=o z6#uh5&XKqyqHiX!VR$n#N%i{!ja&Bjc>Ejwdw59&MVk-KQPc6E5_Uj*-mlmK#-5NraxeZEp9!nqZ&Axxl`;N1T?`L~4 zUmZ67heL2`*l(Sl*0UdHGvk+8Q^b#yA>g^9a{~TjZizv(`g0FjWYl+gjh^+mzyGQn zni2L@(7%qM;4*fQ$)P64cSv#J{(kH32 z(s1VD#h$#DMH!V=#~C|9=)6nCqL>SDG4mR6vHAUJRr82&@#4hGl$YoDn)>Qdz{%$BX9kvy$ekHl?BfGv%!QlCe3zcWTnD{({r3n3r#twC6Opw6{tArPT}^1Ks=&o|$=4D|>VQ zWArfp_OL|2&Z9yl(I*=(En>0#T5B_=nKOAS+hZnWr+33>PDiED(G5{uwaJ;#OTY{&a>pukV%anh z8kS1EL{|RG!N=TjZalv(DEQQ)RWtpLZmF}enGLpi21>?2?;2`&q^M1=G}cPw#Jc^2 zzArfde+oAzJ|8c8v$nyV-N){4_sy7dH1==MUDn5cZqtlk_+2(VE!3>F+4%QY46bgs zJ)UCb(;*efc-l}?PJJgt?rczTndY=|;q`|-n~3!Me$QVW27zZcUV}0~hmDl)!q_>sctFhI>^17$U8+crE%Gp^SO`p>_NF-8aVw@@Fv&4!=%2C}4C+q?|}uEp$xMwo(?U*eNu$X3St z{iTd$R-v)qRvE2FZ^Wwkf?r^RYUal*^X)>hlsvI~d{N<}&#T0V z{948tmyt{~uZ(709CaYC!|3+7W%+=J-k4^>-(%xT?B+Hy(dy5}yKo7E6E&2K9d7;o zmeVHblSc`Zt~2;dbsx|3z>|M_9ocs!@CdsFo*5e*18szYzP$7nNqzQ~s7uXv#=fob zq(4gJo^c6e6EmZ}oRBb__-KcXyDO@)x1(V;vv0H7<{Jbpq`fZot@^HwJw$lUJaLmP z?orw0YTT-PFVB3JNfH?Nr1Zd%v9;eZF+57dF4jrnoN`n#H5?0ATY9Waj4S z|6`qt!QSAQK^CixJmnzE6|KsF-}D8oZ-;Es-gp_m@Y3;0=E=M2!&{DZOOa^D?wcua z1?Mi354R?*ku2t+8)NjDu3;v0hhajoYB@aJvEJ6HTDXn88C}zodAYf0Ri^if{_zH5 zqA|VHAAcGD5m!yIv3noY5-QNQp6=v`^Q1j0PFXE9`=B-dxry;9Co&yxY-fkgg7IgJ!(p5^K}WFQ8RjMpZKZ1 zCGGM`>FSkUR{dkkUgObEVz>i|}t*w(aSCSJEHJ={sF>l>OGKfLEEyoa-Ai`uwI3k=}A%e@Vn& z>YFyd&F7W)%jT2cj-`_-dS6Q%vIUA?s?OM$JITE~^YPo}nfBvfDPkJ>AK4R}nm){| zMryQ&wpO&CK@-{Q9w{5Xv$>OxE#L_7FRK3N#lFUO66X^P6YU&)zc{9HK=$-gId~`d za=1~HvP zTH)%mDfu`Fv6AW>Z_o|A#Z$679d3&U=v?xJ)6Mh3jc3C}c)2|&W1Q~syA2*DDDOo5 z-NVcHq+kf#&)U}BFbJB)H4&~1Z0sk1`0->W(uRx)c)bckHnfW!O03fLo(1}+RV21z zcgJxiC=)2j6Bx-MyGD1ONv%CbGiil4l;c!GGx1C@E{u6W5Mh^GL%1- zjMB8;yjZ8~P+g1%Ro(=<02h2@fJ&3yw!Bl#57L5_B?}S&+uDeD4st*c3(x?(yIJH( zaU8%uK5bcU0v!70<*>BXp=i;3?})D;9SbhdQ6>gNglYQA((y67iBFv1_#JaMQxruR z3Z@}#5=}@Z_|A=HsHv|XM3BNvIj4|2KCLT(WTtRovXL>!iUj6D;)Ep>^Z#|8(1?3# zv-pRx03CjwnKXwn0lU7zkYQ4>ASt_O-0L$MJIylujmnNV3+A^6E~DycjEwR9r!tB+Ho?t+*9> zf51T6w5&vflQp&hj{&WVLs>5%E*06fJS`7VZ-(aic|-Jf;&)r?r{2#Up3}eLEI9G1 z99P+ZN6RAwOi?MNsp-MD^l4rZ{PE>RPmq`KmLv}VuR}WN#DJ0wNw2H6Q(OX?%*;PZ zTU%WMbRuK`@QOCLrV%^TJrUq3?R`r>NCe|koD&+0{}}^Hg6vw4!%G+E@Z3#uc*1VX z?sWzG!D*O;Ed#}YwZy5a2LDYS;U%Rm?BOjXR;Bxq>E4-7VY!Cm4IsUc`d-uN*?}sK z4>f4OMJfUHPk$Wv4YG^Y{PlhIJS9yB<@Q1!yefbPkib3ZjtBE8LO3tx5_5_V02IzM z0UvTq5SI9qr3UD(mp`Ox?II=r{@)i>cCZHa=__PKL<2oHV%7%o%ex#;tWoMp@xZR0 zD7CaabcbYoy8oLX3X4AuhnWlv)@`!I$bNFvJdBWzNU9Cn#%?hCHet`Yia(>P=-AMais z4MK}qACXcjpSCk&DEuCLZ*msT=OhMD3XNpoqBC+N+UI_p3!kS7nJ8lvO@-W%>0ycR za}-5XHt0k(5hMeP^e61o)ioriG!?I2DoG_H0SBuDvOJy^P$X556dWBI8xhx20ad#j z;yrY}f~i=ZnlTx;(~2GZX_+?39wv5wt>;R{j)MNpYbO?WVSO|tDM%!tAHMqxIjGA| z8W3k7b^6x`>BoQdUC6|a*EmA%Zp1Rn0M%t$C_;7=`(o`BHk;5-b!QLZ?fQoe-*Sy|m{mRd7HU)K%q zY{IDqzQAKY1URe!3JC5Z&}C#X;Auf$hl*72?VC=Q*osH(&ztOq$LezhUxp{oSRC@F5sqoEZg;O3GqTx(`Z}(ebZ{%9ewPh>x#!SK1{Y4~ z57ES-api1v!Idd&g%Xk_i9X)IDbEWKFP{5AkyOBCf)I(vfD#Q!DOL3;U#~QonL)#? zGcN(C3K>A`h8C&*g(G|2&H~i<@EP@X(x4gYa8VpEDMX9I1)m}3QJ;8o=RVl8hs5tn zE0|oNpZ+c}#7Z#*XM=FW?~mk39sS|z0}}>DWOJzFy$I42+(tW{9j;y>M_(u`-Sbv>T9RPdejp@$60WJL9d2P53WL*59D$WcSN5upq532Z=xu#tS61fJ;7oCydmjLRpM(GKJ(M#0&GSNK??58V+a3~6oZ88>LXs2 zzE0bjye53t=U(b8-Xr&YR1`#v8#v2QK+F#-1EgIR;DX?zvkSaUE|f4%RNU@sq(XpI zhq@%J!@m%pQQwf5qFjtuD=Dt<24+?gfD3p7#C@R5I18ASKI z3Xg}T9VGeaD7=W}qi@Le@QADM_hcy`N?NA8Bus+uf;SyMsjglRECk<;LvFg_=J!4s z>^3B6r>9l*u>HL?p}#z?DUriKSvob@wWtc@Q$Sdn?zRY)P|}01=_`t;Z&a5@wvabUgt6$@P6JD)i3%|kk6ltlhkH{{)n63oUHx?Q7 zCdlL;oE;&!-Lpj zfMT3cx5bQxad;+H{gtXO+_7+;8AzxzA?e`9@*1F~fIB45i;kkDFv7t)-6NsOKtZHu zBjd)gJBdH%D zz9%tQXY2QufEuktV4DWQH3=8C)eVOjs=$ITPEgX7NJXiCTQ0+Ah6c_IF9SQFzJ8>4 zB{@CQGJ_Amb~CtDw#o~Zu7F~_G{AqP-H2<#QrjC?jB*mN5wEzLp$9Q!4LtfH>Dz(H z?&IRBs<9b4{GC}xsa5Sl=YP%uZVsVR+YKM_{6n3yc`Q zGF!v~fJ?JMM2J|EX4R;t1dou7& z#)#a95 zbLqfE%9AY6^`2G{56RSQ6Q%aw%#H`=>YPtrxaN7v}LJQ%5ZyI}jKvLi*od667 z+NP+QRBn`G``L5sRL!Yv7*vOq5& zziO&2hTp(-XNk1DA+8i1PWR`ipdu!?)FIuf^4ME48c;HJqt-DBY9!^p!qr3Rg{}ei zg+@~FVoyaxtBNvEb;>~6_NZAjpq@LNUO&z`k{}K9%l3wrdIHrXPLe^!9&Cb*lj~{l zLmSiGV>u`nJ;FH<7p|CCeyCXw6_YPV)}bsESUi0K&_ms-)eu&A=!#^U7Rds3P_y$V z#5g=1%k(Nrs<_I^L$c2O`kMa|j3&HzW=pV^szi#7Ue@{`aV7 zCNk*@x-8Ty!AjvX&cE3pGJj@{T�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