From de38aea16439ab7e92c90da69ed6f0505224b371 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 22 Jun 2019 15:45:20 +0200 Subject: [PATCH 1/7] Fix sequence of loading trades --- freqtrade/data/btanalysis.py | 38 +++++++++++++++++++------------- freqtrade/plot/plotting.py | 2 ++ freqtrade/tests/test_plotting.py | 3 ++- scripts/plot_dataframe.py | 5 +++-- 4 files changed, 30 insertions(+), 18 deletions(-) diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index 4aecf8ecf..5efb10433 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -73,37 +73,45 @@ def evaluate_result_multi(results: pd.DataFrame, freq: str, max_open_trades: int return df_final[df_final['pair'] > max_open_trades] -def load_trades(db_url: str = None, exportfilename: str = None) -> pd.DataFrame: +def load_trades_from_db(db_url: str) -> pd.DataFrame: """ - Load trades, either from a DB (using dburl) or via a backtest export file. + Load trades from a DB (using dburl) :param db_url: Sqlite url (default format sqlite:///tradesv3.dry-run.sqlite) - :param exportfilename: Path to a file exported from backtesting :returns: Dataframe containing Trades """ - timeZone = pytz.UTC - trades: pd.DataFrame = pd.DataFrame([], columns=BT_DATA_COLUMNS) + persistence.init(db_url, clean_open_orders=False) + columns = ["pair", "profit", "open_time", "close_time", + "open_rate", "close_rate", "duration"] - if db_url: - persistence.init(db_url, clean_open_orders=False) - columns = ["pair", "profit", "open_time", "close_time", - "open_rate", "close_rate", "duration"] - - for x in Trade.query.all(): - logger.info("date: {}".format(x.open_date)) + for x in Trade.query.all(): + logger.info("date: {}".format(x.open_date)) trades = pd.DataFrame([(t.pair, t.calc_profit(), - t.open_date.replace(tzinfo=timeZone), - t.close_date.replace(tzinfo=timeZone) if t.close_date else None, + t.open_date.replace(tzinfo=pytz.UTC), + t.close_date.replace(tzinfo=pytz.UTC) if t.close_date else None, t.open_rate, t.close_rate, t.close_date.timestamp() - t.open_date.timestamp() if t.close_date else None) for t in Trade.query.all()], columns=columns) - elif exportfilename: + return trades + +def load_trades(exportfilename: str = None, db_url: str = None) -> pd.DataFrame: + """ + Load trades, either from a DB (using dburl) or via a backtest export file. + :param exportfilename: Path to a file exported from backtesting + :param db_url: Sqlite url (default format sqlite:///tradesv3.dry-run.sqlite) + :returns: Dataframe containing Trades + """ + + trades: pd.DataFrame = pd.DataFrame([], columns=BT_DATA_COLUMNS) + if exportfilename: trades = load_backtest_data(Path(exportfilename)) + elif db_url: + trades = load_trades_from_db(db_url) return trades diff --git a/freqtrade/plot/plotting.py b/freqtrade/plot/plotting.py index 94c0830bf..c058f7fb2 100644 --- a/freqtrade/plot/plotting.py +++ b/freqtrade/plot/plotting.py @@ -81,6 +81,8 @@ def plot_trades(fig, trades: pd.DataFrame): ) fig.append_trace(trade_buys, 1, 1) fig.append_trace(trade_sells, 1, 1) + else: + logger.warning("No trades found.") return fig diff --git a/freqtrade/tests/test_plotting.py b/freqtrade/tests/test_plotting.py index 15ab698d8..ec81b93b8 100644 --- a/freqtrade/tests/test_plotting.py +++ b/freqtrade/tests/test_plotting.py @@ -67,11 +67,12 @@ def test_generate_row(default_conf, caplog): assert log_has_re(r'Indicator "no_indicator" ignored\..*', caplog.record_tuples) -def test_plot_trades(): +def test_plot_trades(caplog): fig1 = generage_empty_figure() # nothing happens when no trades are available fig = plot_trades(fig1, None) assert fig == fig1 + assert log_has("No trades found.", caplog.record_tuples) pair = "ADA/BTC" filename = history.make_testdata_path(None) / "backtest-result_test.json" trades = load_backtest_data(filename) diff --git a/scripts/plot_dataframe.py b/scripts/plot_dataframe.py index 4aacc99dd..99ba60d40 100755 --- a/scripts/plot_dataframe.py +++ b/scripts/plot_dataframe.py @@ -125,8 +125,9 @@ def analyse_and_plot_pairs(config: Dict[str, Any]): tickers[pair] = data dataframe = generate_dataframe(strategy, tickers, pair) - trades = load_trades(db_url=config["db_url"], - exportfilename=config["exportfilename"]) + trades = load_trades(exportfilename=config["exportfilename"], + db_url=config["db_url"], + ) trades = trades.loc[trades['pair'] == pair] trades = extract_trades_of_period(dataframe, trades) From 8758218b09152f90286dba5fa0bfafa01441ea29 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 22 Jun 2019 16:18:22 +0200 Subject: [PATCH 2/7] Add data-analysis documentation --- docs/backtesting.md | 18 +----------------- docs/data-analysis.md | 44 +++++++++++++++++++++++++++++++++++++++++++ mkdocs.yml | 1 + 3 files changed, 46 insertions(+), 17 deletions(-) create mode 100644 docs/data-analysis.md diff --git a/docs/backtesting.md b/docs/backtesting.md index 5a25bc255..8d8ea8030 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -221,24 +221,8 @@ strategies, your configuration, and the crypto-currency you have set up. ### Further backtest-result analysis To further analyze your backtest results, you can [export the trades](#exporting-trades-to-file). -You can then load the trades to perform further analysis. +You can then load the trades to perform further analysis as shown in our [data analysis](data-analysis.md#backtesting) backtesting section. -A good way for this is using Jupyter (notebook or lab) - which provides an interactive environment to analyze the data. - -Freqtrade provides an easy to load the backtest results, which is `load_backtest_data` - and takes a path to the backtest-results file. - -``` python -from freqtrade.data.btanalysis import load_backtest_data -df = load_backtest_data("user_data/backtest-result.json") - -# Show value-counts per pair -df.groupby("pair")["sell_reason"].value_counts() - -``` - -This will allow you to drill deeper into your backtest results, and perform analysis which would make the regular backtest-output unreadable. - -If you have some ideas for interesting / helpful backtest data analysis ideas, please submit a PR so the community can benefit from it. ## Backtesting multiple strategies diff --git a/docs/data-analysis.md b/docs/data-analysis.md new file mode 100644 index 000000000..ef3a02355 --- /dev/null +++ b/docs/data-analysis.md @@ -0,0 +1,44 @@ +# Analyzing bot data + +After performing backtests, or after running the bot for some time, it will be interresting to analyze the results your bot generated. + +A good way for this is using Jupyter (notebook or lab) - which provides an interactive environment to analyze the data. + +The following helpers will help you loading the data into Pandas DataFrames, and may also give you some starting points in analyzing the results. + +## Backtesting + +To analyze your backtest results, you can [export the trades](#exporting-trades-to-file). +You can then load the trades to perform further analysis. + +A good way for this is using Jupyter (notebook or lab) - which provides an interactive environment to analyze the data. + +Freqtrade provides an easy to load the backtest results, which is `load_backtest_data` - and takes a path to the backtest-results file. + +``` python +from freqtrade.data.btanalysis import load_backtest_data +df = load_backtest_data("user_data/backtest-result.json") + +# Show value-counts per pair +df.groupby("pair")["sell_reason"].value_counts() + +``` + +This will allow you to drill deeper into your backtest results, and perform analysis which would make the regular backtest-output unreadable. + +If you have some ideas for interesting / helpful backtest data analysis ideas, please submit a PR so the community can benefit from it. + +## Live data + +To analyze the trades your bot generated, you can load them to a DataFrame as follwos: + +``` python +from freqtrade.data.btanalysis import load_trades_from_db + +df = load_trades_from_db("sqlite:///tradesv3.sqlite") + +df.groupby("pair")["sell_reason"].value_counts() + +``` + +Feel free to submit an issue or Pull Request if you would like to share ideas on how to best analyze the data. diff --git a/mkdocs.yml b/mkdocs.yml index 6b445ee3a..b5e759432 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -17,6 +17,7 @@ nav: - Plotting: plotting.md - Deprecated features: deprecated.md - FAQ: faq.md + - Data Analysis: data-analysis.md - SQL Cheatsheet: sql_cheatsheet.md - Sandbox testing: sandbox-testing.md - Contributors guide: developer.md From 3e61ada34a0e1d2b7545e640453991c52f6ff5d5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 22 Jun 2019 16:18:49 +0200 Subject: [PATCH 3/7] Be explicit in what is used, db or trades --- freqtrade/arguments.py | 7 +++++++ freqtrade/configuration.py | 3 ++- scripts/plot_dataframe.py | 14 ++++++++------ 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/freqtrade/arguments.py b/freqtrade/arguments.py index 3075dd0fe..d623b7d7a 100644 --- a/freqtrade/arguments.py +++ b/freqtrade/arguments.py @@ -515,3 +515,10 @@ class Arguments(object): default=750, type=int, ) + parser.add_argument( + '--trade-source', + help='Specify the source for trades (Can be DB or file (backtest file)) Default: %(default)s', + dest='trade_source', + default="file", + choices=["DB", "file"] + ) diff --git a/freqtrade/configuration.py b/freqtrade/configuration.py index b2c35c977..d74b712c3 100644 --- a/freqtrade/configuration.py +++ b/freqtrade/configuration.py @@ -358,7 +358,8 @@ class Configuration(object): self._args_to_config(config, argname='plot_limit', logstring='Limiting plot to: {}') - + self._args_to_config(config, argname='trade_source', + logstring='Using trades from: {}') return config def _validate_config_schema(self, conf: Dict[str, Any]) -> Dict[str, Any]: diff --git a/scripts/plot_dataframe.py b/scripts/plot_dataframe.py index 99ba60d40..828373cb2 100755 --- a/scripts/plot_dataframe.py +++ b/scripts/plot_dataframe.py @@ -33,10 +33,10 @@ import pandas as pd from freqtrade.arguments import Arguments, TimeRange from freqtrade.data import history -from freqtrade.data.btanalysis import load_trades, extract_trades_of_period +from freqtrade.data.btanalysis import (extract_trades_of_period, + load_backtest_data, load_trades_from_db) from freqtrade.optimize import setup_configuration -from freqtrade.plot.plotting import (generate_graph, - generate_plot_file) +from freqtrade.plot.plotting import generate_graph, generate_plot_file from freqtrade.resolvers import ExchangeResolver, StrategyResolver from freqtrade.state import RunMode @@ -124,10 +124,12 @@ def analyse_and_plot_pairs(config: Dict[str, Any]): tickers = {} tickers[pair] = data dataframe = generate_dataframe(strategy, tickers, pair) + trades = None + if config["trade_source"] == "DB": + trades = load_trades_from_db(config["db_url"]) + elif config["trade_source"] == "file": + trades = load_backtest_data(Path(config["exportfilename"])) - trades = load_trades(exportfilename=config["exportfilename"], - db_url=config["db_url"], - ) trades = trades.loc[trades['pair'] == pair] trades = extract_trades_of_period(dataframe, trades) From 559d5ebd1deae0f4cf521d2d88077e0dbdb8c552 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 22 Jun 2019 16:20:41 +0200 Subject: [PATCH 4/7] Remove combined load-method since it's confusing --- docs/plotting.md | 2 +- freqtrade/data/btanalysis.py | 43 ++++++++----------------- freqtrade/tests/data/test_btanalysis.py | 12 ++----- 3 files changed, 17 insertions(+), 40 deletions(-) diff --git a/docs/plotting.md b/docs/plotting.md index 6dc3d13b1..b8e041d61 100644 --- a/docs/plotting.md +++ b/docs/plotting.md @@ -58,7 +58,7 @@ Timerange doesn't work with live data. To plot trades stored in a database use `--db-url` argument: ``` bash -python3 scripts/plot_dataframe.py --db-url sqlite:///tradesv3.dry_run.sqlite -p BTC/ETH +python3 scripts/plot_dataframe.py --db-url sqlite:///tradesv3.dry_run.sqlite -p BTC/ETH --trade-source DB ``` To plot trades from a backtesting result, use `--export-filename ` diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index 5efb10433..9d6d90cf3 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -82,36 +82,21 @@ def load_trades_from_db(db_url: str) -> pd.DataFrame: trades: pd.DataFrame = pd.DataFrame([], columns=BT_DATA_COLUMNS) persistence.init(db_url, clean_open_orders=False) columns = ["pair", "profit", "open_time", "close_time", - "open_rate", "close_rate", "duration"] + "open_rate", "close_rate", "duration", "sell_reason", + "max_rate", "min_rate"] - for x in Trade.query.all(): - logger.info("date: {}".format(x.open_date)) - - trades = pd.DataFrame([(t.pair, t.calc_profit(), - t.open_date.replace(tzinfo=pytz.UTC), - t.close_date.replace(tzinfo=pytz.UTC) if t.close_date else None, - t.open_rate, t.close_rate, - t.close_date.timestamp() - t.open_date.timestamp() - if t.close_date else None) - for t in Trade.query.all()], - columns=columns) - - return trades - - -def load_trades(exportfilename: str = None, db_url: str = None) -> pd.DataFrame: - """ - Load trades, either from a DB (using dburl) or via a backtest export file. - :param exportfilename: Path to a file exported from backtesting - :param db_url: Sqlite url (default format sqlite:///tradesv3.dry-run.sqlite) - :returns: Dataframe containing Trades - """ - - trades: pd.DataFrame = pd.DataFrame([], columns=BT_DATA_COLUMNS) - if exportfilename: - trades = load_backtest_data(Path(exportfilename)) - elif db_url: - trades = load_trades_from_db(db_url) + trades = pd.DataFrame([(t.pair, t.calc_profit(), + t.open_date.replace(tzinfo=pytz.UTC), + t.close_date.replace(tzinfo=pytz.UTC) if t.close_date else None, + t.open_rate, t.close_rate, + t.close_date.timestamp() - t.open_date.timestamp() + if t.close_date else None, + t.sell_reason, + t.max_rate, + t.min_rate, + ) + for t in Trade.query.all()], + columns=columns) return trades diff --git a/freqtrade/tests/data/test_btanalysis.py b/freqtrade/tests/data/test_btanalysis.py index 6fa529394..1cb48393d 100644 --- a/freqtrade/tests/data/test_btanalysis.py +++ b/freqtrade/tests/data/test_btanalysis.py @@ -7,7 +7,7 @@ from pandas import DataFrame, to_datetime from freqtrade.arguments import TimeRange from freqtrade.data.btanalysis import (BT_DATA_COLUMNS, extract_trades_of_period, - load_backtest_data, load_trades) + load_backtest_data, load_trades_from_db) from freqtrade.data.history import load_pair_history, make_testdata_path from freqtrade.tests.test_persistence import create_mock_trades @@ -28,14 +28,6 @@ def test_load_backtest_data(): load_backtest_data(str("filename") + "nofile") -def test_load_trades_file(default_conf, fee, mocker): - # Real testing of load_backtest_data is done in test_load_backtest_data - lbt = mocker.patch("freqtrade.data.btanalysis.load_backtest_data", MagicMock()) - filename = make_testdata_path(None) / "backtest-result_test.json" - load_trades(db_url=None, exportfilename=filename) - assert lbt.call_count == 1 - - @pytest.mark.usefixtures("init_persistence") def test_load_trades_db(default_conf, fee, mocker): @@ -43,7 +35,7 @@ def test_load_trades_db(default_conf, fee, mocker): # remove init so it does not init again init_mock = mocker.patch('freqtrade.persistence.init', MagicMock()) - trades = load_trades(db_url=default_conf['db_url'], exportfilename=None) + trades = load_trades_from_db(db_url=default_conf['db_url']) assert init_mock.call_count == 1 assert len(trades) == 3 assert isinstance(trades, DataFrame) From cc56d0e0fc75bc7de7afdb140ae9618d71a0ee78 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 22 Jun 2019 16:40:33 +0200 Subject: [PATCH 5/7] Remove unneeded initialization --- freqtrade/arguments.py | 3 ++- scripts/plot_dataframe.py | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/arguments.py b/freqtrade/arguments.py index d623b7d7a..f6fb2dedb 100644 --- a/freqtrade/arguments.py +++ b/freqtrade/arguments.py @@ -517,7 +517,8 @@ class Arguments(object): ) parser.add_argument( '--trade-source', - help='Specify the source for trades (Can be DB or file (backtest file)) Default: %(default)s', + help='Specify the source for trades (Can be DB or file (backtest file)) ' + 'Default: %(default)s', dest='trade_source', default="file", choices=["DB", "file"] diff --git a/scripts/plot_dataframe.py b/scripts/plot_dataframe.py index 828373cb2..e5cc6ef89 100755 --- a/scripts/plot_dataframe.py +++ b/scripts/plot_dataframe.py @@ -124,7 +124,6 @@ def analyse_and_plot_pairs(config: Dict[str, Any]): tickers = {} tickers[pair] = data dataframe = generate_dataframe(strategy, tickers, pair) - trades = None if config["trade_source"] == "DB": trades = load_trades_from_db(config["db_url"]) elif config["trade_source"] == "file": From 11d39bb0d3f3a151ea41bba085f9b031d552ea09 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 24 Jun 2019 17:20:41 +0200 Subject: [PATCH 6/7] Improve wording --- docs/data-analysis.md | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/docs/data-analysis.md b/docs/data-analysis.md index ef3a02355..6d440ffa6 100644 --- a/docs/data-analysis.md +++ b/docs/data-analysis.md @@ -1,6 +1,6 @@ # Analyzing bot data -After performing backtests, or after running the bot for some time, it will be interresting to analyze the results your bot generated. +After performing backtests, or after running the bot for some time, it will be interesting to analyze the results your bot generated. A good way for this is using Jupyter (notebook or lab) - which provides an interactive environment to analyze the data. @@ -11,9 +11,7 @@ The following helpers will help you loading the data into Pandas DataFrames, and To analyze your backtest results, you can [export the trades](#exporting-trades-to-file). You can then load the trades to perform further analysis. -A good way for this is using Jupyter (notebook or lab) - which provides an interactive environment to analyze the data. - -Freqtrade provides an easy to load the backtest results, which is `load_backtest_data` - and takes a path to the backtest-results file. +Freqtrade provides the `load_backtest_data()` helper function to easily load the backtest results, which takes the path to the the backtest-results file as parameter. ``` python from freqtrade.data.btanalysis import load_backtest_data @@ -24,13 +22,13 @@ df.groupby("pair")["sell_reason"].value_counts() ``` -This will allow you to drill deeper into your backtest results, and perform analysis which would make the regular backtest-output unreadable. +This will allow you to drill deeper into your backtest results, and perform analysis which would make the regular backtest-output very difficult to digest due to information overload. -If you have some ideas for interesting / helpful backtest data analysis ideas, please submit a PR so the community can benefit from it. +If you have some ideas for interesting / helpful backtest data analysis ideas, please submit a Pull Request so the community can benefit from it. ## Live data -To analyze the trades your bot generated, you can load them to a DataFrame as follwos: +To analyze the trades your bot generated, you can load them to a DataFrame as follows: ``` python from freqtrade.data.btanalysis import load_trades_from_db @@ -41,4 +39,4 @@ df.groupby("pair")["sell_reason"].value_counts() ``` -Feel free to submit an issue or Pull Request if you would like to share ideas on how to best analyze the data. +Feel free to submit an issue or Pull Request enhancing this document if you would like to share ideas on how to best analyze the data. From e83f8941a19ba5282fdeed105c7475afbecd6f71 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 24 Jun 2019 19:20:42 +0200 Subject: [PATCH 7/7] Fix documentation grammar --- docs/data-analysis.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/data-analysis.md b/docs/data-analysis.md index 6d440ffa6..1940fa3e6 100644 --- a/docs/data-analysis.md +++ b/docs/data-analysis.md @@ -22,7 +22,7 @@ df.groupby("pair")["sell_reason"].value_counts() ``` -This will allow you to drill deeper into your backtest results, and perform analysis which would make the regular backtest-output very difficult to digest due to information overload. +This will allow you to drill deeper into your backtest results, and perform analysis which otherwise would make the regular backtest-output very difficult to digest due to information overload. If you have some ideas for interesting / helpful backtest data analysis ideas, please submit a Pull Request so the community can benefit from it.