From ccf3c6987435827af3649d19909fe78a0a3f4f25 Mon Sep 17 00:00:00 2001 From: Jonathan Raviotta Date: Thu, 8 Aug 2019 22:09:15 -0400 Subject: [PATCH] edits to clarify backtesting analysis --- docs/data-analysis.md | 70 +++++--- setup.py | 12 +- user_data/notebooks/analysis_example.ipynb | 195 +++++++++++++-------- 3 files changed, 176 insertions(+), 101 deletions(-) diff --git a/docs/data-analysis.md b/docs/data-analysis.md index 826c34747..3b07d77a6 100644 --- a/docs/data-analysis.md +++ b/docs/data-analysis.md @@ -2,11 +2,33 @@ You can analyze the results of backtests and trading history easily using Jupyter notebooks. A sample notebook is located at `user_data/notebooks/analysis_example.ipynb`. For usage instructions, see [jupyter.org](https://jupyter.org/documentation). +## Example snippets + +### Load backtest results into a pandas dataframe + +```python +# Load backtest results +df = load_backtest_data("user_data/backtest_data/backtest-result.json") + +# Show value-counts per pair +df.groupby("pair")["sell_reason"].value_counts() +``` + +### Load live trading results into a pandas dataframe + +``` python +# Fetch trades from database +df = load_trades_from_db("sqlite:///tradesv3.sqlite") + +# Display results +df.groupby("pair")["sell_reason"].value_counts() +``` + ## Strategy debugging example Debugging a strategy can be time-consuming. FreqTrade offers helper functions to visualize raw data. -### Import requirements and define variables used in the script +### Import requirements and define variables used in analyses ```python # Imports @@ -47,12 +69,6 @@ print("Loaded " + str(len(bt_data)) + f" rows of data for {pair} from {data_loca ### Load and run strategy * Rerun each time the strategy file is changed -* Display the trade details. Note that using `data.head()` would also work, however most indicators have some "startup" data at the top of the dataframe. - -Some possible problems: - -* Columns with NaN values at the end of the dataframe -* Columns used in `crossed*()` functions with completely different units ```python # Load strategy using values set above @@ -60,33 +76,31 @@ strategy = StrategyResolver({'strategy': strategy_name, 'user_data_dir': user_data_dir, 'strategy_path': strategy_location}).strategy -# Run strategy (just like in backtesting) +# Generate buy/sell signals using strategy df = strategy.analyze_ticker(bt_data, {'pair': pair}) +``` +### Display the trade details + +* Note that using `data.head()` would also work, however most indicators have some "startup" data at the top of the dataframe. + +#### Some possible problems + +* Columns with NaN values at the end of the dataframe +* Columns used in `crossed*()` functions with completely different units + +#### Comparison with full backtest + +having 200 buy signals as output for one pair from `analyze_ticker()` does not necessarily mean that 200 trades will be made during backtesting. + +Assuming you use only one condition such as, `df['rsi'] < 30` as buy condition, this will generate multiple "buy" signals for each pair in sequence (until rsi returns > 29). +The bot will only buy on the first of these signals (and also only if a trade-slot ("max_open_trades") is still available), or on one of the middle signals, as soon as a "slot" becomes available. + +```python # Report results print(f"Generated {df['buy'].sum()} buy signals") data = df.set_index('date', drop=True) data.tail() ``` -### Load backtest results into a pandas dataframe - -```python -# Load backtest results -df = load_backtest_data("user_data/backtest_data/backtest-result.json") - -# Show value-counts per pair -df.groupby("pair")["sell_reason"].value_counts() -``` - -### Load live trading results into a pandas dataframe - -``` python -# Fetch trades from database -df = load_trades_from_db("sqlite:///tradesv3.sqlite") - -# Display results -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/setup.py b/setup.py index b46101c0f..6ac7a117f 100644 --- a/setup.py +++ b/setup.py @@ -25,7 +25,15 @@ develop = [ 'pytest-random-order', ] -all_extra = api + plot + develop +jupyter = [ + 'jupyter', + 'nbstripout', + 'ipykernel', + 'isort', + 'yapf', + ] + +all_extra = api + plot + develop + jupyter setup(name='freqtrade', version=__version__, @@ -68,7 +76,7 @@ setup(name='freqtrade', 'dev': all_extra, 'plot': plot, 'all': all_extra, - 'jupyter': [], + 'jupyter': jupyter, }, include_package_data=True, diff --git a/user_data/notebooks/analysis_example.ipynb b/user_data/notebooks/analysis_example.ipynb index d14575e97..b3a61bc51 100644 --- a/user_data/notebooks/analysis_example.ipynb +++ b/user_data/notebooks/analysis_example.ipynb @@ -4,31 +4,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Strategy debugging example" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Change directory\n", - "# Define all paths relative to the project root shown in the cell output\n", - "import os\n", - "from pathlib import Path\n", - "try:\n", - "\tos.chdir(Path(os.getcwd(), '../..'))\n", - "\tprint(os.getcwd())\n", - "except:\n", - "\tpass" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Import requirements and define variables used in the script" + "# Analyzing bot data\n", + "\n", + "You can analyze the results of backtests and trading history easily using Jupyter notebooks. A sample notebook is located at `user_data/notebooks/analysis_example.ipynb`. For usage instructions, see [jupyter.org](https://jupyter.org/documentation)." ] }, { @@ -39,11 +17,97 @@ "source": [ "# Imports\n", "from pathlib import Path\n", + "import os\n", "from freqtrade.data.history import load_pair_history\n", "from freqtrade.resolvers import StrategyResolver\n", "from freqtrade.data.btanalysis import load_backtest_data\n", - "from freqtrade.data.btanalysis import load_trades_from_db\n", + "from freqtrade.data.btanalysis import load_trades_from_db" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Change directory\n", + "# Define all paths relative to the project root shown in the cell output\n", + "try:\n", + "\tos.chdir(Path(Path.cwd(), '../..'))\n", + "\tprint(Path.cwd())\n", + "except:\n", + "\tpass" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Example snippets" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Load backtest results into a pandas dataframe" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Load backtest results\n", + "df = load_backtest_data(\"user_data/backtest_data/backtest-result.json\")\n", "\n", + "# Show value-counts per pair\n", + "df.groupby(\"pair\")[\"sell_reason\"].value_counts()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Load live trading results into a pandas dataframe" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Fetch trades from database\n", + "df = load_trades_from_db(\"sqlite:///tradesv3.sqlite\")\n", + "\n", + "# Display results\n", + "df.groupby(\"pair\")[\"sell_reason\"].value_counts()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Strategy debugging example\n", + "\n", + "Debugging a strategy can be time-consuming. FreqTrade offers helper functions to visualize raw data." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Import requirements and define variables used in analyses" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ "# Define some constants\n", "ticker_interval = \"1m\"\n", "# Name of the strategy class\n", @@ -51,9 +115,9 @@ "# Path to user data\n", "user_data_dir = 'user_data'\n", "# Location of the strategy\n", - "strategy_location = Path(user_data_dir, 'strategies')\n", + "strategy_location = os.path.join(user_data_dir, 'strategies')\n", "# Location of the data\n", - "data_location = Path(user_data_dir, 'data', 'binance')\n", + "data_location = os.path.join(user_data_dir, 'data', 'binance')\n", "# Pair to analyze \n", "# Only use one pair here\n", "pair = \"BTC_USDT\"" @@ -85,15 +149,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Load and run strategy \n", - "\n", - "* Rerun each time the strategy file is changed\n", - "* Display the trade details. Note that using `data.head()` would also work, however most indicators have some \"startup\" data at the top of the dataframe.\n", - "\n", - "Some possible problems:\n", - "\n", - "* Columns with NaN values at the end of the dataframe\n", - "* Columns used in `crossed*()` functions with completely different units" + "### Load and run strategy\n", + "* Rerun each time the strategy file is changed" ] }, { @@ -107,53 +164,49 @@ " 'user_data_dir': user_data_dir,\n", " 'strategy_path': strategy_location}).strategy\n", "\n", - "# Run strategy (just like in backtesting)\n", - "df = strategy.analyze_ticker(bt_data, {'pair': pair})\n", + "# Generate buy/sell signals using strategy\n", + "df = strategy.analyze_ticker(bt_data, {'pair': pair})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Display the trade details\n", + "* Note that using `data.head()` would also work, however most indicators have some \"startup\" data at the top of the dataframe.\n", "\n", + "#### Some possible problems\n", + "\n", + "* Columns with NaN values at the end of the dataframe\n", + "* Columns used in `crossed*()` functions with completely different units\n", + "\n", + "#### Comparison with full backtest\n", + "\n", + "having 200 buy signals as output for one pair from `analyze_ticker()` does not necessarily mean that 200 trades will be made during backtesting.\n", + "\n", + "Assuming you use only one condition such as, `df['rsi'] < 30` as buy condition, this will generate multiple \"buy\" signals for each pair in sequence (until rsi returns > 29).\n", + "The bot will only buy on the first of these signals (and also only if a trade-slot (\"max_open_trades\") is still available), or on one of the middle signals, as soon as a \"slot\" becomes available.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ "# Report results\n", "print(f\"Generated {df['buy'].sum()} buy signals\")\n", "data = df.set_index('date', drop=True)\n", "data.tail()" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Load backtest results into a pandas dataframe" - ] - }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "# Load backtest results\n", - "df = load_backtest_data(\"user_data/backtest_data/backtest-result.json\")\n", - "\n", - "# Show value-counts per pair\n", - "df.groupby(\"pair\")[\"sell_reason\"].value_counts()\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Load live trading results into a pandas dataframe" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Fetch trades from database\n", - "df = load_trades_from_db(\"sqlite:///tradesv3.sqlite\")\n", - "\n", - "# Display results\n", - "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." ] } ],