311 lines
10 KiB
Plaintext
311 lines
10 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"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": [
|
|
"## Setup"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Change directory\n",
|
|
"Jupyter notebooks execute from the current directory. Change working directory to the project root so that relative paths work."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"import os\n",
|
|
"from pathlib import Path\n",
|
|
"\n",
|
|
"# Modify this cell to insure that the output shows the correct path.\n",
|
|
"project_root = \"somedir/freqtrade\"\n",
|
|
"i = 0\n",
|
|
"try:\n",
|
|
" os.chdirdir(project_root)\n",
|
|
" assert Path('LICENSE').is_file()\n",
|
|
"except:\n",
|
|
" while i < 4 and (not Path('LICENSE').is_file()):\n",
|
|
" os.chdir(Path(Path.cwd(), '../'))\n",
|
|
" i += 1\n",
|
|
" project_root = Path.cwd()\n",
|
|
"print(Path.cwd())"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Fix asycronous execution\n",
|
|
"Jupyter notebooks get confused with async operations. This fixes the problem."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"import nest_asyncio\n",
|
|
"\n",
|
|
"# Fix asyncio for Jupyter\n",
|
|
"nest_asyncio.apply()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Generate configuration\n",
|
|
"This combines all config files into a single dictionary. Individual options can be manipulated with dict indexing.\n",
|
|
"Watch out for type conversion pitfalls. The config dict created here has string values. More work needs to be done to automatically coerce strings to the expected object types. You will need to perform type conversion when passing function arguments as demonstrated in the following sections."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"from freqtrade.configuration import Configuration\n",
|
|
"from freqtrade.configuration.timerange import TimeRange\n",
|
|
"\n",
|
|
"# Load configuration\n",
|
|
"# Specify values for use in this script\n",
|
|
"############### Customize to match your needs. ##################\n",
|
|
"config_files = [\n",
|
|
" Path('user_data', 'user_repo', 'config.json'),\n",
|
|
" Path(Path.home(), '.freqtrade', 'exchange-config.json')\n",
|
|
"]\n",
|
|
"# Create config object\n",
|
|
"config = Configuration.from_files(config_files)\n",
|
|
"\n",
|
|
"############### Customize to match your needs. ##################\n",
|
|
"# Define some constants\n",
|
|
"\n",
|
|
"# These values must be included in config.json or set here\n",
|
|
"# Name of the strategy class\n",
|
|
"config['strategy'] = 'DefaultStrategy'\n",
|
|
"# Path to user data\n",
|
|
"config['user_data_dir'] = Path('user_data')\n",
|
|
"# Location of the ticker data\n",
|
|
"config['datadir'] = Path('user_data', 'data/binance')\n",
|
|
"# Location of the strategy\n",
|
|
"config['strategy_path'] = Path('user_data', 'strategies')\n",
|
|
"# Specify backtest results to load\n",
|
|
"config['trade_source'] = 'file'\n",
|
|
"config['exportfilename'] = Path('user_data', 'backtest_results/backtest-result.json')\n",
|
|
"config['db_url'] = 'sqlite://'\n",
|
|
"\n",
|
|
"# Specify interval to analyze\n",
|
|
"ticker_interval = \"5m\"\n",
|
|
"# Specify timerange to test\n",
|
|
"timerange = '-100'\n",
|
|
"# Pair to analyze - Only use one pair here\n",
|
|
"pair = \"ETH/BTC\"\n",
|
|
"# Indicators from strategy to plot\n",
|
|
"overlay_indicators = ['ema50', 'ema100']\n",
|
|
"bottom_indicators = ['macd']"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Generate config argument for CLI\n",
|
|
"CLI commands can be executed from a notebook cell by including `!` before the command. This combines all config files into a variable that can be called in a cli command as `{conf}`"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"from itertools import chain\n",
|
|
"\n",
|
|
"# Create config string for use in cli commands\n",
|
|
"conf = \" \".join(\n",
|
|
" list(chain.from_iterable([['-c', str(file)] for file in config_files])))\n",
|
|
"\n",
|
|
"# Example cli command\n",
|
|
"!freqtrade {conf} backtesting --help"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Configure logging\n",
|
|
"This imports the freqtrade logger format and displays logging messages from the internal functions."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"import logging\n",
|
|
"import json\n",
|
|
"from freqtrade.loggers import setup_logging\n",
|
|
"\n",
|
|
"# Configure logging\n",
|
|
"logger = logging.getLogger()\n",
|
|
"setup_logging(config)\n",
|
|
"logger.setLevel(logging.INFO)\n",
|
|
"logger.info(f'conf: {conf}')"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Visualize buy/sell signals\n",
|
|
"This uses the values set in the configuration section to:\n",
|
|
"* Load the history of the specified pair\n",
|
|
"* Load the specified strategy\n",
|
|
"* Generate buy and sell signals produced by the specified strategy\n",
|
|
"* Return a dictionary with the figure, figure dataframe and trades dataframe\n",
|
|
" * Key is the same as the figure filename. EG: 'freqtrade-plot-ETH_BTC-15m.html'\n",
|
|
" * Display elements with dict indexing as demonstrated below\n",
|
|
" * `fig.show()` displays the chart inline `fig.show(renderer=\"browser\")` displays the chart in a tab.\n",
|
|
"\n",
|
|
"*Note: `data.head()` may include empty values for indicators that require a startup period. This is expected behavior. For example `ma5` requires 5 candles before computing the first average*\n",
|
|
"\n",
|
|
"### 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",
|
|
"* 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. "
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"from freqtrade.plot.plotting import load_and_plot_trades\n",
|
|
"\n",
|
|
"plot_data = load_and_plot_trades({'strategy': config['strategy'],\n",
|
|
" 'strategy_path': Path(config['strategy_path']),\n",
|
|
" 'timerange': timerange,\n",
|
|
" 'ticker_interval': ticker_interval,\n",
|
|
" 'strategy_path': Path(config['strategy_path']),\n",
|
|
" 'datadir': Path(config['datadir']),\n",
|
|
" 'user_data_dir': Path(config['user_data_dir']),\n",
|
|
" 'exchange': config['exchange'],\n",
|
|
" 'trade_source': config['trade_source'],\n",
|
|
" 'exportfilename': config['exportfilename'],\n",
|
|
" 'indicators1': overlay_indicators,\n",
|
|
" 'indicators2': bottom_indicators\n",
|
|
" })"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Display charts and data inline\n",
|
|
"for idx in list(plot_data.keys()):\n",
|
|
" print(idx)\n",
|
|
" plot_data[idx]['fig'].show()\n",
|
|
" display(plot_data[idx]['data'].tail())\n",
|
|
" display(plot_data[idx]['trades'].head())"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Detailed analysis\n",
|
|
"Slice and dice your data to generate insights"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"def show_indicator_extremes(df, indicator):\n",
|
|
" '''\n",
|
|
" Sorts simulation dataframe by specified indicator\n",
|
|
" '''\n",
|
|
" df = df[df[indicator].notna()].sort_values(by=indicator, ascending=False)\n",
|
|
" return df\n",
|
|
"\n",
|
|
"# Demonstrate with first item\n",
|
|
"idx = list(plot_data.keys())[0]\n",
|
|
"indicator = overlay_indicators[0]\n",
|
|
"print(idx)\n",
|
|
"data = plot_data[idx]['data']\n",
|
|
"extremes = show_indicator_extremes(data, indicator)\n",
|
|
"display(extremes.head(10))\n",
|
|
"display(extremes.tail(10))"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Run Backtest\n",
|
|
"Once you are happy with your strategy signals, run a backtest then plot again."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"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."
|
|
]
|
|
}
|
|
],
|
|
"metadata": {
|
|
"file_extension": ".py",
|
|
"kernelspec": {
|
|
"display_name": "Python 3",
|
|
"language": "python",
|
|
"name": "python3"
|
|
},
|
|
"language_info": {
|
|
"codemirror_mode": {
|
|
"name": "ipython"
|
|
},
|
|
"file_extension": ".py",
|
|
"mimetype": "text/x-python",
|
|
"name": "python",
|
|
"nbconvert_exporter": "python"
|
|
},
|
|
"mimetype": "text/x-python",
|
|
"name": "python",
|
|
"npconvert_exporter": "python",
|
|
"pygments_lexer": "ipython3",
|
|
"version": 3
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 2
|
|
}
|