diff --git a/freqtrade/plot/plotting.py b/freqtrade/plot/plotting.py index d17b0fb8f..3b6673a2d 100644 --- a/freqtrade/plot/plotting.py +++ b/freqtrade/plot/plotting.py @@ -332,13 +332,14 @@ def load_and_plot_trades(config: Dict[str, Any]): - Load trades excecuted during the selected period - Generate Plotly plot objects - Generate plot files - :return: None + :return: Dict of fig, data, trades for each pair and interval """ strategy = StrategyResolver(config).strategy plot_elements = init_plotscript(config) trades = plot_elements['trades'] pair_counter = 0 + plot_data = {} for pair, data in plot_elements["tickers"].items(): pair_counter += 1 logger.info("analyse pair %s", pair) @@ -359,16 +360,23 @@ def load_and_plot_trades(config: Dict[str, Any]): store_plot_file(fig, filename=generate_plot_filename(pair, config['ticker_interval']), directory=config['user_data_dir'] / "plot") + plot_data[generate_plot_filename(pair, + config['ticker_interval'])] = {"fig": fig, + "data": dataframe, + "trades": trades} logger.info('End of plotting process. %s plots generated', pair_counter) + logger.info(f'fig, data, trades are available with the keys {list(plot_data)}') + return plot_data -def plot_profit(config: Dict[str, Any]) -> None: +def plot_profit(config: Dict[str, Any], auto_open: bool = False) -> None: """ Plots the total profit for all pairs. Note, the profit calculation isn't realistic. But should be somewhat proportional, and therefor useful in helping out to find a good algorithm. + :return: profit plot """ plot_elements = init_plotscript(config) trades = load_trades(config['trade_source'], @@ -382,4 +390,5 @@ def plot_profit(config: Dict[str, Any]) -> None: # this could be useful to gauge the overall market trend fig = generate_profit_graph(plot_elements["pairs"], plot_elements["tickers"], trades) store_plot_file(fig, filename='freqtrade-profit-plot.html', - directory=config['user_data_dir'] / "plot", auto_open=True) + directory=config['user_data_dir'] / "plot", auto_open=auto_open) + return fig diff --git a/user_data/notebooks/strategy_analysis_example.ipynb b/user_data/notebooks/strategy_analysis_example.ipynb index 6b55db9c8..56e21374d 100644 --- a/user_data/notebooks/strategy_analysis_example.ipynb +++ b/user_data/notebooks/strategy_analysis_example.ipynb @@ -89,31 +89,38 @@ "# Specify values for use in this script\n", "############### Customize to match your needs. ##################\n", "config_files = [\n", - " Path('user_data', 'config.json'),\n", - " Path(Path.home(), '.freqtrade', 'config.json')\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", - "ticker_interval = \"5m\"\n", - "# Path to user data\n", - "user_data_dir = Path('user_data')\n", - "# Location of the ticker data\n", - "datadir = Path(user_data_dir, 'data/binance')\n", + "\n", + "# These values must be included in config.json or set here\n", "# Name of the strategy class\n", - "strategy_name = 'DefaultStrategy'\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", - "strategy_path = Path(user_data_dir, 'strategies')\n", + "config['strategy_path'] = Path('user_data', 'strategies')\n", "# Specify backtest results to load\n", - "trade_source = 'file'\n", - "exportfilename = Path(user_data_dir, 'backtest_results/backtest-result.json')\n", - "db_url = 'sqlite://'\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\"" + "pair = \"ETH/BTC\"\n", + "# Indicators from strategy to plot\n", + "overlay_indicators = ['ema50', 'ema100']\n", + "bottom_indicators = ['macd']" ] }, { @@ -155,17 +162,14 @@ "outputs": [], "source": [ "import logging\n", - "\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}')\n", - "\n", - "# Show config in memory\n", - "logger.info(json.dumps(config, indent=1))" + "logger.info(f'conf: {conf}')" ] }, { @@ -177,8 +181,10 @@ "* Load the history of the specified pair\n", "* Load the specified strategy\n", "* Generate buy and sell signals produced by the specified strategy\n", - "* Plot the results\n", - "\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", @@ -199,66 +205,21 @@ "metadata": {}, "outputs": [], "source": [ - "from pathlib import Path\n", + "from freqtrade.plot.plotting import load_and_plot_trades\n", "\n", - "import pandas as pd\n", - "\n", - "from freqtrade.data.btanalysis import load_trades\n", - "from freqtrade.data.history import load_pair_history\n", - "from freqtrade.resolvers import StrategyResolver\n", - "from freqtrade.plot.plotting import extract_trades_of_period, generate_candlestick_graph\n", - "# Load ticker history\n", - "tickers = load_pair_history(pair=pair,\n", - " ticker_interval=ticker_interval,\n", - " datadir=datadir,\n", - " timerange=TimeRange.parse_timerange(timerange))\n", - "\n", - "# Confirm success\n", - "print(\"Loaded \" + str(len(tickers)) +\n", - " f\" rows of data for {pair} from {datadir}\")\n", - "\n", - "# Load strategy\n", - "strategy = StrategyResolver({\n", - " 'strategy': strategy_name,\n", - " 'user_data_dir': user_data_dir,\n", - " 'strategy_path': strategy_path\n", - "}).strategy\n", - "\n", - "# Generate buy/sell signals using strategy\n", - "data = strategy.analyze_ticker(tickers, {'pair': pair})\n", - "logger.info(f'Indicators: {list(data)[6:-2]}')\n", - "\n", - "# Collect trades if a backtest has been completed\n", - "try:\n", - " trades = load_trades(source=trade_source,\n", - " db_url=db_url,\n", - " exportfilename=exportfilename)\n", - " trades = trades.loc[trades['pair'] == pair]\n", - " trades = extract_trades_of_period(data, trades)\n", - "except:\n", - " trades = pd.DataFrame()\n", - "\n", - "# Build and display plot\n", - "# Specify the indicators to plot as lists\n", - "# indicators1 is a list of indicators to overlay on the price chart\n", - "# indicators2 is a list of indicators to plot below the price chart\n", - "fig = generate_candlestick_graph(\n", - " pair=pair,\n", - " data=data,\n", - " trades=trades,\n", - " indicators1=['ema20', 'ema50', 'ema100'],\n", - " indicators2=['macd', 'macdsignal'])\n", - "\n", - "fig.show()\n", - "display(data.tail())" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Run Backtest\n", - "Once you are happy with your strategy signals, run a backtest then plot again." + "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", + " })" ] }, { @@ -267,8 +228,51 @@ "metadata": {}, "outputs": [], "source": [ - "# Run backtest\n", - "!freqtrade {conf} backtesting --timerange={timerange} --ticker-interval {ticker_interval} --export=trades --export-filename={exportfilename}" + "# 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." ] }, {