From dac88c6aedf8582ea608df7471720cc83e015727 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 30 Oct 2019 13:35:55 +0100 Subject: [PATCH 1/4] extract Find parallel trades per interval --- freqtrade/data/btanalysis.py | 32 ++++++++++++++++++++++-------- tests/optimize/test_backtesting.py | 6 +++--- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index 0f5d395ff..9dbd69e3e 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -52,16 +52,17 @@ def load_backtest_data(filename) -> pd.DataFrame: return df -def evaluate_result_multi(results: pd.DataFrame, freq: str, max_open_trades: int) -> pd.DataFrame: +def parallel_trade_analysis(results: pd.DataFrame, timeframe: str) -> pd.DataFrame: """ Find overlapping trades by expanding each trade once per period it was open - and then counting overlaps + and then counting overlaps. :param results: Results Dataframe - can be loaded - :param freq: Frequency used for the backtest - :param max_open_trades: parameter max_open_trades used during backtest run - :return: dataframe with open-counts per time-period in freq + :param timeframe: Timeframe used for backtest + :return: dataframe with open-counts per time-period in timeframe """ - dates = [pd.Series(pd.date_range(row[1].open_time, row[1].close_time, freq=freq)) + from freqtrade.exchange import timeframe_to_minutes + timeframe_min = timeframe_to_minutes(timeframe) + dates = [pd.Series(pd.date_range(row[1].open_time, row[1].close_time, freq=f"{timeframe_min}min")) for row in results[['open_time', 'close_time']].iterrows()] deltas = [len(x) for x in dates] dates = pd.Series(pd.concat(dates).values, name='date') @@ -69,8 +70,23 @@ def evaluate_result_multi(results: pd.DataFrame, freq: str, max_open_trades: int df2 = pd.concat([dates, df2], axis=1) df2 = df2.set_index('date') - df_final = df2.resample(freq)[['pair']].count() - return df_final[df_final['pair'] > max_open_trades] + df_final = df2.resample(f"{timeframe_min}min")[['pair']].count() + df_final = df_final.rename({'pair': 'open_trades'}, axis=1) + return df_final + + +def evaluate_result_multi(results: pd.DataFrame, timeframe: str, + max_open_trades: int) -> pd.DataFrame: + """ + Find overlapping trades by expanding each trade once per period it was open + and then counting overlaps + :param results: Results Dataframe - can be loaded + :param timeframe: Frequency used for the backtest + :param max_open_trades: parameter max_open_trades used during backtest run + :return: dataframe with open-counts per time-period in freq + """ + df_final = parallel_trade_analysis(results, timeframe) + return df_final[df_final['open_trades'] > max_open_trades] def load_trades_from_db(db_url: str) -> pd.DataFrame: diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index ba87848ec..5912c5489 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -714,9 +714,9 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir) results = backtesting.backtest(backtest_conf) # Make sure we have parallel trades - assert len(evaluate_result_multi(results, '5min', 2)) > 0 + assert len(evaluate_result_multi(results, '5m', 2)) > 0 # make sure we don't have trades with more than configured max_open_trades - assert len(evaluate_result_multi(results, '5min', 3)) == 0 + assert len(evaluate_result_multi(results, '5m', 3)) == 0 backtest_conf = { 'stake_amount': default_conf['stake_amount'], @@ -727,7 +727,7 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir) 'end_date': max_date, } results = backtesting.backtest(backtest_conf) - assert len(evaluate_result_multi(results, '5min', 1)) == 0 + assert len(evaluate_result_multi(results, '5m', 1)) == 0 def test_backtest_record(default_conf, fee, mocker): From dd408aa5d63df6e02884616910da9b9fc7eb1378 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 30 Oct 2019 14:07:23 +0100 Subject: [PATCH 2/4] Add analyze_trade_parallelism analysis function --- freqtrade/data/btanalysis.py | 7 ++++--- tests/data/test_btanalysis.py | 15 +++++++++++++-- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index 9dbd69e3e..b9625e745 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -52,7 +52,7 @@ def load_backtest_data(filename) -> pd.DataFrame: return df -def parallel_trade_analysis(results: pd.DataFrame, timeframe: str) -> pd.DataFrame: +def analyze_trade_parallelism(results: pd.DataFrame, timeframe: str) -> pd.DataFrame: """ Find overlapping trades by expanding each trade once per period it was open and then counting overlaps. @@ -62,7 +62,8 @@ def parallel_trade_analysis(results: pd.DataFrame, timeframe: str) -> pd.DataFra """ from freqtrade.exchange import timeframe_to_minutes timeframe_min = timeframe_to_minutes(timeframe) - dates = [pd.Series(pd.date_range(row[1].open_time, row[1].close_time, freq=f"{timeframe_min}min")) + dates = [pd.Series(pd.date_range(row[1].open_time, row[1].close_time, + freq=f"{timeframe_min}min")) for row in results[['open_time', 'close_time']].iterrows()] deltas = [len(x) for x in dates] dates = pd.Series(pd.concat(dates).values, name='date') @@ -85,7 +86,7 @@ def evaluate_result_multi(results: pd.DataFrame, timeframe: str, :param max_open_trades: parameter max_open_trades used during backtest run :return: dataframe with open-counts per time-period in freq """ - df_final = parallel_trade_analysis(results, timeframe) + df_final = analyze_trade_parallelism(results, timeframe) return df_final[df_final['open_trades'] > max_open_trades] diff --git a/tests/data/test_btanalysis.py b/tests/data/test_btanalysis.py index 78781cffd..b49344bbd 100644 --- a/tests/data/test_btanalysis.py +++ b/tests/data/test_btanalysis.py @@ -10,7 +10,7 @@ from freqtrade.data.btanalysis import (BT_DATA_COLUMNS, create_cum_profit, extract_trades_of_period, load_backtest_data, load_trades, - load_trades_from_db) + load_trades_from_db, analyze_trade_parallelism) from freqtrade.data.history import load_data, load_pair_history from tests.test_persistence import create_mock_trades @@ -32,7 +32,7 @@ def test_load_backtest_data(testdatadir): @pytest.mark.usefixtures("init_persistence") -def test_load_trades_db(default_conf, fee, mocker): +def test_load_trades_from_db(default_conf, fee, mocker): create_mock_trades(fee) # remove init so it does not init again @@ -84,6 +84,17 @@ def test_extract_trades_of_period(testdatadir): assert trades1.iloc[-1].close_time == Arrow(2017, 11, 14, 15, 25, 0).datetime +def test_analyze_trade_parallelism(default_conf, mocker, testdatadir): + filename = testdatadir / "backtest-result_test.json" + bt_data = load_backtest_data(filename) + + res = analyze_trade_parallelism(bt_data, "5m") + assert isinstance(res, DataFrame) + assert 'open_trades' in res.columns + assert res['open_trades'].max() == 3 + assert res['open_trades'].min() == 0 + + def test_load_trades(default_conf, mocker): db_mock = mocker.patch("freqtrade.data.btanalysis.load_trades_from_db", MagicMock()) bt_mock = mocker.patch("freqtrade.data.btanalysis.load_backtest_data", MagicMock()) From 6928c685a8ee8894b5494974f90ae079dcae9fc1 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 30 Oct 2019 14:12:41 +0100 Subject: [PATCH 3/4] Add documentation sample for parallel_trade_analysis --- docs/strategy_analysis_example.md | 16 ++++++++++ .../notebooks/strategy_analysis_example.ipynb | 29 +++++++++++++++++-- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/docs/strategy_analysis_example.md b/docs/strategy_analysis_example.md index 49800bbb3..55f1bd908 100644 --- a/docs/strategy_analysis_example.md +++ b/docs/strategy_analysis_example.md @@ -107,6 +107,22 @@ trades = load_trades_from_db("sqlite:///tradesv3.sqlite") trades.groupby("pair")["sell_reason"].value_counts() ``` +## Analyze the loaded trades for trade parallelism +This can be useful to find the best `max_open_trades` parameter, when used with backtesting in conjunction with `--disable-max-market-positions`. + +`parallel_trade_analysis()` returns a timeseries dataframe with an "open_trades" column, specifying the number of open trades for each candle. + + +```python +from freqtrade.data.btanalysis import parallel_trade_analysis + +# Analyze the above +parallel_trades = parallel_trade_analysis(trades, '5m') + + +parallel_trades.plot() +``` + ## Plot results Freqtrade offers interactive plotting capabilities based on plotly. diff --git a/user_data/notebooks/strategy_analysis_example.ipynb b/user_data/notebooks/strategy_analysis_example.ipynb index b9576e0bb..edb05a7ca 100644 --- a/user_data/notebooks/strategy_analysis_example.ipynb +++ b/user_data/notebooks/strategy_analysis_example.ipynb @@ -68,9 +68,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "scrolled": true - }, + "metadata": {}, "outputs": [], "source": [ "# Load strategy using values set above\n", @@ -169,6 +167,31 @@ "trades.groupby(\"pair\")[\"sell_reason\"].value_counts()" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Analyze the loaded trades for trade parallelism\n", + "This can be useful to find the best `max_open_trades` parameter, when used with backtesting in conjunction with `--disable-max-market-positions`.\n", + "\n", + "`parallel_trade_analysis()` returns a timeseries dataframe with an \"open_trades\" column, specifying the number of open trades for each candle." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from freqtrade.data.btanalysis import parallel_trade_analysis\n", + "\n", + "# Analyze the above\n", + "parallel_trades = parallel_trade_analysis(trades, '5m')\n", + "\n", + "\n", + "parallel_trades.plot()" + ] + }, { "cell_type": "markdown", "metadata": {}, From bba8e614094605c254bed875f0a846026305a437 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 30 Oct 2019 19:30:35 +0100 Subject: [PATCH 4/4] Rename function in samples --- docs/strategy_analysis_example.md | 6 +++--- user_data/notebooks/strategy_analysis_example.ipynb | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/strategy_analysis_example.md b/docs/strategy_analysis_example.md index 55f1bd908..aa4578ca7 100644 --- a/docs/strategy_analysis_example.md +++ b/docs/strategy_analysis_example.md @@ -110,14 +110,14 @@ trades.groupby("pair")["sell_reason"].value_counts() ## Analyze the loaded trades for trade parallelism This can be useful to find the best `max_open_trades` parameter, when used with backtesting in conjunction with `--disable-max-market-positions`. -`parallel_trade_analysis()` returns a timeseries dataframe with an "open_trades" column, specifying the number of open trades for each candle. +`analyze_trade_parallelism()` returns a timeseries dataframe with an "open_trades" column, specifying the number of open trades for each candle. ```python -from freqtrade.data.btanalysis import parallel_trade_analysis +from freqtrade.data.btanalysis import analyze_trade_parallelism # Analyze the above -parallel_trades = parallel_trade_analysis(trades, '5m') +parallel_trades = analyze_trade_parallelism(trades, '5m') parallel_trades.plot() diff --git a/user_data/notebooks/strategy_analysis_example.ipynb b/user_data/notebooks/strategy_analysis_example.ipynb index edb05a7ca..03dc83b4e 100644 --- a/user_data/notebooks/strategy_analysis_example.ipynb +++ b/user_data/notebooks/strategy_analysis_example.ipynb @@ -174,7 +174,7 @@ "## Analyze the loaded trades for trade parallelism\n", "This can be useful to find the best `max_open_trades` parameter, when used with backtesting in conjunction with `--disable-max-market-positions`.\n", "\n", - "`parallel_trade_analysis()` returns a timeseries dataframe with an \"open_trades\" column, specifying the number of open trades for each candle." + "`analyze_trade_parallelism()` returns a timeseries dataframe with an \"open_trades\" column, specifying the number of open trades for each candle." ] }, { @@ -183,10 +183,10 @@ "metadata": {}, "outputs": [], "source": [ - "from freqtrade.data.btanalysis import parallel_trade_analysis\n", + "from freqtrade.data.btanalysis import analyze_trade_parallelism\n", "\n", "# Analyze the above\n", - "parallel_trades = parallel_trade_analysis(trades, '5m')\n", + "parallel_trades = analyze_trade_parallelism(trades, '5m')\n", "\n", "\n", "parallel_trades.plot()"