474 lines
14 KiB
Plaintext
474 lines
14 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Strategy analysis example\n",
|
|
"\n",
|
|
"Debugging a strategy can be time-consuming. Freqtrade offers helper functions to visualize raw data.\n",
|
|
"The following assumes you work with SampleStrategy, data for 5m timeframe from Binance and have downloaded them into the data directory in the default location.\n",
|
|
"Please follow the [documentation](https://www.freqtrade.io/en/stable/data-download/) for more details."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Setup\n",
|
|
"\n",
|
|
"### Change Working directory to repository root"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"import os\n",
|
|
"from pathlib import Path\n",
|
|
"\n",
|
|
"# Change directory\n",
|
|
"# Modify this cell to insure that the output shows the correct path.\n",
|
|
"# Define all paths relative to the project root shown in the cell output\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": [
|
|
"### Configure Freqtrade environment"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"from freqtrade.configuration import Configuration\n",
|
|
"\n",
|
|
"# Customize these according to your needs.\n",
|
|
"\n",
|
|
"# Initialize empty configuration object\n",
|
|
"config = Configuration.from_files([])\n",
|
|
"# Optionally (recommended), use existing configuration file\n",
|
|
"# config = Configuration.from_files([\"user_data/config.json\"])\n",
|
|
"\n",
|
|
"# Define some constants\n",
|
|
"config[\"timeframe\"] = \"5m\"\n",
|
|
"# Name of the strategy class\n",
|
|
"config[\"strategy\"] = \"SampleStrategy\"\n",
|
|
"# Location of the data\n",
|
|
"data_location = config[\"datadir\"]\n",
|
|
"# Pair to analyze - Only use one pair here\n",
|
|
"pair = \"BTC/USDT\""
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Load data using values set above\n",
|
|
"from freqtrade.data.history import load_pair_history\n",
|
|
"from freqtrade.enums import CandleType\n",
|
|
"\n",
|
|
"candles = load_pair_history(datadir=data_location,\n",
|
|
" timeframe=config[\"timeframe\"],\n",
|
|
" pair=pair,\n",
|
|
" data_format = \"json\", # Make sure to update this to your data\n",
|
|
" candle_type=CandleType.SPOT,\n",
|
|
" )\n",
|
|
"\n",
|
|
"# Confirm success\n",
|
|
"print(f\"Loaded {len(candles)} rows of data for {pair} from {data_location}\")\n",
|
|
"candles.head()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Load and run strategy\n",
|
|
"* Rerun each time the strategy file is changed"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Load strategy using values set above\n",
|
|
"from freqtrade.resolvers import StrategyResolver\n",
|
|
"from freqtrade.data.dataprovider import DataProvider\n",
|
|
"strategy = StrategyResolver.load_strategy(config)\n",
|
|
"strategy.dp = DataProvider(config, None, None)\n",
|
|
"\n",
|
|
"# Generate buy/sell signals using strategy\n",
|
|
"df = strategy.analyze_ticker(candles, {'pair': pair})\n",
|
|
"df.tail()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Display the trade details\n",
|
|
"\n",
|
|
"* Note that using `data.head()` would also work, however most indicators have some \"startup\" data at the top of the dataframe.\n",
|
|
"* Some possible problems\n",
|
|
" * Columns with NaN values at the end of the dataframe\n",
|
|
" * Columns used in `crossed*()` functions with completely different units\n",
|
|
"* Comparison with full backtest\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. \n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Report results\n",
|
|
"print(f\"Generated {df['enter_long'].sum()} entry signals\")\n",
|
|
"data = df.set_index('date', drop=False)\n",
|
|
"data.tail()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Load existing objects into a Jupyter notebook\n",
|
|
"\n",
|
|
"The following cells assume that you have already generated data using the cli. \n",
|
|
"They will allow you to drill deeper into your results, and perform analysis which otherwise would make the output very difficult to digest due to information overload."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Load backtest results to pandas dataframe\n",
|
|
"\n",
|
|
"Analyze a trades dataframe (also used below for plotting)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"from freqtrade.data.btanalysis import load_backtest_data, load_backtest_stats\n",
|
|
"\n",
|
|
"# if backtest_dir points to a directory, it'll automatically load the last backtest file.\n",
|
|
"backtest_dir = config[\"user_data_dir\"] / \"backtest_results\"\n",
|
|
"# backtest_dir can also point to a specific file \n",
|
|
"# backtest_dir = config[\"user_data_dir\"] / \"backtest_results/backtest-result-2020-07-01_20-04-22.json\""
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# You can get the full backtest statistics by using the following command.\n",
|
|
"# This contains all information used to generate the backtest result.\n",
|
|
"stats = load_backtest_stats(backtest_dir)\n",
|
|
"\n",
|
|
"strategy = 'SampleStrategy'\n",
|
|
"# All statistics are available per strategy, so if `--strategy-list` was used during backtest, this will be reflected here as well.\n",
|
|
"# Example usages:\n",
|
|
"print(stats['strategy'][strategy]['results_per_pair'])\n",
|
|
"# Get pairlist used for this backtest\n",
|
|
"print(stats['strategy'][strategy]['pairlist'])\n",
|
|
"# Get market change (average change of all pairs from start to end of the backtest period)\n",
|
|
"print(stats['strategy'][strategy]['market_change'])\n",
|
|
"# Maximum drawdown ()\n",
|
|
"print(stats['strategy'][strategy]['max_drawdown'])\n",
|
|
"# Maximum drawdown start and end\n",
|
|
"print(stats['strategy'][strategy]['drawdown_start'])\n",
|
|
"print(stats['strategy'][strategy]['drawdown_end'])\n",
|
|
"\n",
|
|
"\n",
|
|
"# Get strategy comparison (only relevant if multiple strategies were compared)\n",
|
|
"print(stats['strategy_comparison'])\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Load backtested trades as dataframe\n",
|
|
"trades = load_backtest_data(backtest_dir)\n",
|
|
"\n",
|
|
"# Show value-counts per pair\n",
|
|
"trades.groupby(\"pair\")[\"exit_reason\"].value_counts()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Plotting daily profit / equity line"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Plotting equity line (starting with 0 on day 1 and adding daily profit for each backtested day)\n",
|
|
"\n",
|
|
"from freqtrade.configuration import Configuration\n",
|
|
"from freqtrade.data.btanalysis import load_backtest_data, load_backtest_stats\n",
|
|
"import plotly.express as px\n",
|
|
"import pandas as pd\n",
|
|
"\n",
|
|
"# strategy = 'SampleStrategy'\n",
|
|
"# config = Configuration.from_files([\"user_data/config.json\"])\n",
|
|
"# backtest_dir = config[\"user_data_dir\"] / \"backtest_results\"\n",
|
|
"\n",
|
|
"stats = load_backtest_stats(backtest_dir)\n",
|
|
"strategy_stats = stats['strategy'][strategy]\n",
|
|
"\n",
|
|
"dates = []\n",
|
|
"profits = []\n",
|
|
"for date_profit in strategy_stats['daily_profit']:\n",
|
|
" dates.append(date_profit[0])\n",
|
|
" profits.append(date_profit[1])\n",
|
|
"\n",
|
|
"equity = 0\n",
|
|
"equity_daily = []\n",
|
|
"for daily_profit in profits:\n",
|
|
" equity_daily.append(equity)\n",
|
|
" equity += float(daily_profit)\n",
|
|
"\n",
|
|
"\n",
|
|
"df = pd.DataFrame({'dates': dates,'equity_daily': equity_daily})\n",
|
|
"\n",
|
|
"fig = px.line(df, x=\"dates\", y=\"equity_daily\")\n",
|
|
"fig.show()\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Load live trading results into a pandas dataframe\n",
|
|
"\n",
|
|
"In case you did already some trading and want to analyze your performance"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"from freqtrade.data.btanalysis import load_trades_from_db\n",
|
|
"\n",
|
|
"# Fetch trades from database\n",
|
|
"trades = load_trades_from_db(\"sqlite:///tradesv3.sqlite\")\n",
|
|
"\n",
|
|
"# Display results\n",
|
|
"trades.groupby(\"pair\")[\"exit_reason\"].value_counts()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Analyze the loaded trades for trade parallelism\n",
|
|
"This can be useful to find the best `max_open_trades` parameter, when used with backtesting in conjunction with `--disable-max-market-positions`.\n",
|
|
"\n",
|
|
"`analyze_trade_parallelism()` returns a timeseries dataframe with an \"open_trades\" column, specifying the number of open trades for each candle."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"from freqtrade.data.btanalysis import analyze_trade_parallelism\n",
|
|
"\n",
|
|
"# Analyze the above\n",
|
|
"parallel_trades = analyze_trade_parallelism(trades, '5m')\n",
|
|
"\n",
|
|
"parallel_trades.plot()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Plot results\n",
|
|
"\n",
|
|
"Freqtrade offers interactive plotting capabilities based on plotly."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"from freqtrade.plot.plotting import generate_candlestick_graph\n",
|
|
"# Limit graph period to keep plotly quick and reactive\n",
|
|
"\n",
|
|
"# Filter trades to one pair\n",
|
|
"trades_red = trades.loc[trades['pair'] == pair]\n",
|
|
"\n",
|
|
"data_red = data['2019-06-01':'2019-06-10']\n",
|
|
"# Generate candlestick graph\n",
|
|
"graph = generate_candlestick_graph(pair=pair,\n",
|
|
" data=data_red,\n",
|
|
" trades=trades_red,\n",
|
|
" indicators1=['sma20', 'ema50', 'ema55'],\n",
|
|
" indicators2=['rsi', 'macd', 'macdsignal', 'macdhist']\n",
|
|
" )\n",
|
|
"\n",
|
|
"\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Show graph inline\n",
|
|
"# graph.show()\n",
|
|
"\n",
|
|
"# Render graph in a separate window\n",
|
|
"graph.show(renderer=\"browser\")\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Plot average profit per trade as distribution graph"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"import plotly.figure_factory as ff\n",
|
|
"\n",
|
|
"hist_data = [trades.profit_ratio]\n",
|
|
"group_labels = ['profit_ratio'] # name of the dataset\n",
|
|
"\n",
|
|
"fig = ff.create_distplot(hist_data, group_labels, bin_size=0.01)\n",
|
|
"fig.show()\n"
|
|
]
|
|
},
|
|
{
|
|
"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.9.7 64-bit",
|
|
"language": "python",
|
|
"name": "python3"
|
|
},
|
|
"language_info": {
|
|
"codemirror_mode": {
|
|
"name": "ipython",
|
|
"version": 3
|
|
},
|
|
"file_extension": ".py",
|
|
"mimetype": "text/x-python",
|
|
"name": "python",
|
|
"nbconvert_exporter": "python",
|
|
"pygments_lexer": "ipython3",
|
|
"version": "3.9.7"
|
|
},
|
|
"mimetype": "text/x-python",
|
|
"name": "python",
|
|
"npconvert_exporter": "python",
|
|
"pygments_lexer": "ipython3",
|
|
"toc": {
|
|
"base_numbering": 1,
|
|
"nav_menu": {},
|
|
"number_sections": true,
|
|
"sideBar": true,
|
|
"skip_h1_title": false,
|
|
"title_cell": "Table of Contents",
|
|
"title_sidebar": "Contents",
|
|
"toc_cell": false,
|
|
"toc_position": {},
|
|
"toc_section_display": true,
|
|
"toc_window_display": false
|
|
},
|
|
"varInspector": {
|
|
"cols": {
|
|
"lenName": 16,
|
|
"lenType": 16,
|
|
"lenVar": 40
|
|
},
|
|
"kernels_config": {
|
|
"python": {
|
|
"delete_cmd_postfix": "",
|
|
"delete_cmd_prefix": "del ",
|
|
"library": "var_list.py",
|
|
"varRefreshCmd": "print(var_dic_list())"
|
|
},
|
|
"r": {
|
|
"delete_cmd_postfix": ") ",
|
|
"delete_cmd_prefix": "rm(",
|
|
"library": "var_list.r",
|
|
"varRefreshCmd": "cat(var_dic_list()) "
|
|
}
|
|
},
|
|
"types_to_exclude": [
|
|
"module",
|
|
"function",
|
|
"builtin_function_or_method",
|
|
"instance",
|
|
"_Feature"
|
|
],
|
|
"window_display": false
|
|
},
|
|
"version": 3,
|
|
"vscode": {
|
|
"interpreter": {
|
|
"hash": "675f32a300d6d26767470181ad0b11dd4676bcce7ed1dd2ffe2fbc370c95fc7c"
|
|
}
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 4
|
|
}
|