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..1940fa3e6 --- /dev/null +++ b/docs/data-analysis.md @@ -0,0 +1,42 @@ +# Analyzing bot data + +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. + +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. + +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 +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 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. + +## Live data + +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 + +df = load_trades_from_db("sqlite:///tradesv3.sqlite") + +df.groupby("pair")["sell_reason"].value_counts() + +``` + +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. 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/arguments.py b/freqtrade/arguments.py index 07b4bbb2d..1ec32d1f0 100644 --- a/freqtrade/arguments.py +++ b/freqtrade/arguments.py @@ -516,3 +516,11 @@ 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/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index f78ca3fa8..5a0dee042 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -73,37 +73,30 @@ 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 :return: 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", "sell_reason", + "max_rate", "min_rate"] - 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)) - - 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_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: - - trades = load_backtest_data(Path(exportfilename)) + 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/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/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) 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/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 diff --git a/scripts/plot_dataframe.py b/scripts/plot_dataframe.py index 3792233de..7eaf0b337 100755 --- a/scripts/plot_dataframe.py +++ b/scripts/plot_dataframe.py @@ -33,10 +33,10 @@ import pandas as pd from freqtrade.arguments import Arguments 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 @@ -97,9 +97,11 @@ def analyse_and_plot_pairs(config: Dict[str, Any]): tickers = {} tickers[pair] = data dataframe = generate_dataframe(strategy, tickers, pair) + 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(db_url=config["db_url"], - exportfilename=config["exportfilename"]) trades = trades.loc[trades['pair'] == pair] trades = extract_trades_of_period(dataframe, trades)