Merge pull request #1959 from freqtrade/split_btanalysis_load_trades
Split btanalysis load trades
This commit is contained in:
commit
31a2aac627
@ -221,24 +221,8 @@ strategies, your configuration, and the crypto-currency you have set up.
|
|||||||
### Further backtest-result analysis
|
### Further backtest-result analysis
|
||||||
|
|
||||||
To further analyze your backtest results, you can [export the trades](#exporting-trades-to-file).
|
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
|
## Backtesting multiple strategies
|
||||||
|
|
||||||
|
42
docs/data-analysis.md
Normal file
42
docs/data-analysis.md
Normal file
@ -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.
|
@ -58,7 +58,7 @@ Timerange doesn't work with live data.
|
|||||||
To plot trades stored in a database use `--db-url` argument:
|
To plot trades stored in a database use `--db-url` argument:
|
||||||
|
|
||||||
``` bash
|
``` 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 <filename>`
|
To plot trades from a backtesting result, use `--export-filename <filename>`
|
||||||
|
@ -516,3 +516,11 @@ class Arguments(object):
|
|||||||
default=750,
|
default=750,
|
||||||
type=int,
|
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"]
|
||||||
|
)
|
||||||
|
@ -358,7 +358,8 @@ class Configuration(object):
|
|||||||
|
|
||||||
self._args_to_config(config, argname='plot_limit',
|
self._args_to_config(config, argname='plot_limit',
|
||||||
logstring='Limiting plot to: {}')
|
logstring='Limiting plot to: {}')
|
||||||
|
self._args_to_config(config, argname='trade_source',
|
||||||
|
logstring='Using trades from: {}')
|
||||||
return config
|
return config
|
||||||
|
|
||||||
def _validate_config_schema(self, conf: Dict[str, Any]) -> Dict[str, Any]:
|
def _validate_config_schema(self, conf: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
@ -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]
|
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 db_url: Sqlite url (default format sqlite:///tradesv3.dry-run.sqlite)
|
||||||
:param exportfilename: Path to a file exported from backtesting
|
|
||||||
:return: Dataframe containing Trades
|
:return: Dataframe containing Trades
|
||||||
"""
|
"""
|
||||||
timeZone = pytz.UTC
|
|
||||||
|
|
||||||
trades: pd.DataFrame = pd.DataFrame([], columns=BT_DATA_COLUMNS)
|
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:
|
trades = pd.DataFrame([(t.pair, t.calc_profit(),
|
||||||
persistence.init(db_url, clean_open_orders=False)
|
t.open_date.replace(tzinfo=pytz.UTC),
|
||||||
columns = ["pair", "profit", "open_time", "close_time",
|
t.close_date.replace(tzinfo=pytz.UTC) if t.close_date else None,
|
||||||
"open_rate", "close_rate", "duration"]
|
t.open_rate, t.close_rate,
|
||||||
|
t.close_date.timestamp() - t.open_date.timestamp()
|
||||||
for x in Trade.query.all():
|
if t.close_date else None,
|
||||||
logger.info("date: {}".format(x.open_date))
|
t.sell_reason,
|
||||||
|
t.max_rate,
|
||||||
trades = pd.DataFrame([(t.pair, t.calc_profit(),
|
t.min_rate,
|
||||||
t.open_date.replace(tzinfo=timeZone),
|
)
|
||||||
t.close_date.replace(tzinfo=timeZone) if t.close_date else None,
|
for t in Trade.query.all()],
|
||||||
t.open_rate, t.close_rate,
|
columns=columns)
|
||||||
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))
|
|
||||||
|
|
||||||
return trades
|
return trades
|
||||||
|
|
||||||
|
@ -81,6 +81,8 @@ def plot_trades(fig, trades: pd.DataFrame):
|
|||||||
)
|
)
|
||||||
fig.append_trace(trade_buys, 1, 1)
|
fig.append_trace(trade_buys, 1, 1)
|
||||||
fig.append_trace(trade_sells, 1, 1)
|
fig.append_trace(trade_sells, 1, 1)
|
||||||
|
else:
|
||||||
|
logger.warning("No trades found.")
|
||||||
return fig
|
return fig
|
||||||
|
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ from pandas import DataFrame, to_datetime
|
|||||||
from freqtrade.arguments import TimeRange
|
from freqtrade.arguments import TimeRange
|
||||||
from freqtrade.data.btanalysis import (BT_DATA_COLUMNS,
|
from freqtrade.data.btanalysis import (BT_DATA_COLUMNS,
|
||||||
extract_trades_of_period,
|
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.data.history import load_pair_history, make_testdata_path
|
||||||
from freqtrade.tests.test_persistence import create_mock_trades
|
from freqtrade.tests.test_persistence import create_mock_trades
|
||||||
|
|
||||||
@ -28,14 +28,6 @@ def test_load_backtest_data():
|
|||||||
load_backtest_data(str("filename") + "nofile")
|
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")
|
@pytest.mark.usefixtures("init_persistence")
|
||||||
def test_load_trades_db(default_conf, fee, mocker):
|
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
|
# remove init so it does not init again
|
||||||
init_mock = mocker.patch('freqtrade.persistence.init', MagicMock())
|
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 init_mock.call_count == 1
|
||||||
assert len(trades) == 3
|
assert len(trades) == 3
|
||||||
assert isinstance(trades, DataFrame)
|
assert isinstance(trades, DataFrame)
|
||||||
|
@ -67,11 +67,12 @@ def test_generate_row(default_conf, caplog):
|
|||||||
assert log_has_re(r'Indicator "no_indicator" ignored\..*', caplog.record_tuples)
|
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()
|
fig1 = generage_empty_figure()
|
||||||
# nothing happens when no trades are available
|
# nothing happens when no trades are available
|
||||||
fig = plot_trades(fig1, None)
|
fig = plot_trades(fig1, None)
|
||||||
assert fig == fig1
|
assert fig == fig1
|
||||||
|
assert log_has("No trades found.", caplog.record_tuples)
|
||||||
pair = "ADA/BTC"
|
pair = "ADA/BTC"
|
||||||
filename = history.make_testdata_path(None) / "backtest-result_test.json"
|
filename = history.make_testdata_path(None) / "backtest-result_test.json"
|
||||||
trades = load_backtest_data(filename)
|
trades = load_backtest_data(filename)
|
||||||
|
@ -17,6 +17,7 @@ nav:
|
|||||||
- Plotting: plotting.md
|
- Plotting: plotting.md
|
||||||
- Deprecated features: deprecated.md
|
- Deprecated features: deprecated.md
|
||||||
- FAQ: faq.md
|
- FAQ: faq.md
|
||||||
|
- Data Analysis: data-analysis.md
|
||||||
- SQL Cheatsheet: sql_cheatsheet.md
|
- SQL Cheatsheet: sql_cheatsheet.md
|
||||||
- Sandbox testing: sandbox-testing.md
|
- Sandbox testing: sandbox-testing.md
|
||||||
- Contributors guide: developer.md
|
- Contributors guide: developer.md
|
||||||
|
@ -33,10 +33,10 @@ import pandas as pd
|
|||||||
|
|
||||||
from freqtrade.arguments import Arguments
|
from freqtrade.arguments import Arguments
|
||||||
from freqtrade.data import history
|
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.optimize import setup_configuration
|
||||||
from freqtrade.plot.plotting import (generate_graph,
|
from freqtrade.plot.plotting import generate_graph, generate_plot_file
|
||||||
generate_plot_file)
|
|
||||||
from freqtrade.resolvers import ExchangeResolver, StrategyResolver
|
from freqtrade.resolvers import ExchangeResolver, StrategyResolver
|
||||||
from freqtrade.state import RunMode
|
from freqtrade.state import RunMode
|
||||||
|
|
||||||
@ -97,9 +97,11 @@ def analyse_and_plot_pairs(config: Dict[str, Any]):
|
|||||||
tickers = {}
|
tickers = {}
|
||||||
tickers[pair] = data
|
tickers[pair] = data
|
||||||
dataframe = generate_dataframe(strategy, tickers, pair)
|
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 = trades.loc[trades['pair'] == pair]
|
||||||
trades = extract_trades_of_period(dataframe, trades)
|
trades = extract_trades_of_period(dataframe, trades)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user