From e1f8decdfc276be4b7da57f4e621bd80b2db41c2 Mon Sep 17 00:00:00 2001 From: cardosofede Date: Thu, 7 Dec 2023 17:17:32 -0300 Subject: [PATCH] (feat) create single controller backtes notebook --- .../02_single_controller_backtest.ipynb | 490 ++++++++++++++++++ 1 file changed, 490 insertions(+) create mode 100644 quants_lab/research_notebooks/02_single_controller_backtest.ipynb diff --git a/quants_lab/research_notebooks/02_single_controller_backtest.ipynb b/quants_lab/research_notebooks/02_single_controller_backtest.ipynb new file mode 100644 index 0000000..7333342 --- /dev/null +++ b/quants_lab/research_notebooks/02_single_controller_backtest.ipynb @@ -0,0 +1,490 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "import datetime\n", + "from decimal import Decimal\n", + "\n", + "# Market configuration\n", + "exchange = \"binance_perpetual\"\n", + "trading_pair = \"WLD-USDT\"\n", + "interval = \"3m\"\n", + "\n", + "# Account configuration\n", + "initial_portfolio_usd = 1000\n", + "order_amount = Decimal(\"25\")\n", + "n_levels = 1\n", + "leverage = 20\n", + "trade_cost = 0.0006\n", + "\n", + "# Backtest period\n", + "start = \"2023-01-01\"\n", + "end = \"2024-01-02\"\n", + "\n", + "# Triple barrier configuration\n", + "stop_loss = Decimal(\"0.015\")\n", + "take_profit = Decimal(\"0.03\")\n", + "time_limit = 60 * 60 * 12 # 12 hours\n", + "trailing_stop_activation_price_delta = Decimal(\"0.008\")\n", + "trailing_stop_trailing_delta = Decimal(\"0.004\")" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "from hummingbot.smart_components.utils.order_level_builder import OrderLevelBuilder\n", + "from hummingbot.smart_components.strategy_frameworks.data_types import (\n", + " TripleBarrierConf\n", + ")\n", + "\n", + "# Building the order levels\n", + "order_level_builder = OrderLevelBuilder(n_levels=n_levels)\n", + "order_levels = order_level_builder.build_order_levels(\n", + " amounts=order_amount,\n", + " spreads=Decimal(\"0\"),\n", + " # for directional strategies we don't need spreads since we are going to use market orders to enter\n", + " triple_barrier_confs=TripleBarrierConf(\n", + " stop_loss=stop_loss, take_profit=take_profit, time_limit=time_limit,\n", + " trailing_stop_activation_price_delta=trailing_stop_activation_price_delta,\n", + " trailing_stop_trailing_delta=trailing_stop_trailing_delta),\n", + ")" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "# Let's inpect the order levels\n", + "order_levels" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "import sys\n", + "from hummingbot.data_feed.candles_feed.candles_factory import CandlesConfig\n", + "from quants_lab.controllers.supertrend import SuperTrend, SuperTrendConfig\n", + "\n", + "# Controller configuration\n", + "length = 100\n", + "multiplier = 3.0\n", + "percentage_threshold = 0.01\n", + "\n", + "# Creating the instance of the configuration and the controller\n", + "config = SuperTrendConfig(\n", + " exchange=exchange,\n", + " trading_pair=trading_pair,\n", + " order_levels=order_levels,\n", + " candles_config=[\n", + " CandlesConfig(connector=exchange, trading_pair=trading_pair, interval=interval, max_records=sys.maxsize),\n", + " ],\n", + " leverage=leverage,\n", + " length=length,\n", + " multiplier=multiplier,\n", + " percentage_threshold=percentage_threshold,\n", + ")\n", + "controller = SuperTrend(config=config)" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "from quants_lab.strategy.strategy_analysis import StrategyAnalysis\n", + "\n", + "from hummingbot.smart_components.strategy_frameworks.directional_trading.directional_trading_backtesting_engine import \\\n", + " DirectionalTradingBacktestingEngine\n", + "\n", + "# Creating the backtesting engine and loading the historical data\n", + "engine = DirectionalTradingBacktestingEngine(controller=controller)\n", + "engine.load_controller_data(\"../../data/candles\")" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "# Let's see what is inside the candles of the controller\n", + "engine.controller.candles" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "engine.controller.candles[0].candles_df" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "# Let's understand what is inside the processed data since this is what we are going to use when generating the signal ;)\n", + "engine.controller.get_processed_data()" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "# Let's run the backtesting\n", + "\n", + "backtesting_results = engine.run_backtesting(initial_portfolio_usd=initial_portfolio_usd,\n", + " trade_cost=trade_cost,\n", + " start=start, end=end)" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "# Let's see what is inside the backtesting results\n", + "backtesting_results.keys()" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "# now let's analyze each of the dataframes\n", + "\n", + "# 1. The processed data: this is the data that we are going to use to generate the signal\n", + "backtesting_results[\"processed_data\"]" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "# 2. The executors dataframe: this is the dataframe that contains the information of the orders that were executed\n", + "backtesting_results[\"executors_df\"]" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "# 3. The results dataframe: this is the dataframe that contains the information of the pnl of the strategy\n", + "backtesting_results[\"results\"]" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "# Now let's analyze the results using the StrategyAnalysis class\n", + "strategy_analysis = StrategyAnalysis(\n", + " positions=backtesting_results[\"executors_df\"],\n", + " candles_df=backtesting_results[\"processed_data\"],\n", + ")" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "# let's visualize the PNL over time of the strategy\n", + "strategy_analysis.pnl_over_time()" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "strategy_analysis.create_base_figure(volume=False, positions=False, trade_pnl=True)\n", + "fig = strategy_analysis.figure()" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "fig" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "# Now let's see how we can add the SuperTrend to the plot\n", + "\n", + "import plotly.graph_objects as go\n", + "\n", + "super_trend_long = strategy_analysis.candles_df[strategy_analysis.candles_df[f\"SUPERTd_{length}_{multiplier}\"] == 1]\n", + "super_trend_short = strategy_analysis.candles_df[strategy_analysis.candles_df[f\"SUPERTd_{length}_{multiplier}\"] == -1]\n", + "# Add the SuperTrend line\n", + "fig.add_trace(go.Scatter(x=super_trend_long.index, y=super_trend_long[f'SUPERT_{length}_{multiplier}'],\n", + " mode='markers',\n", + " name='SuperTrend Long',\n", + " line=dict(color=\"green\")),\n", + " row=1, col=1)\n", + "# Add the SuperTrend line\n", + "fig.add_trace(go.Scatter(x=super_trend_short.index, y=super_trend_short[f'SUPERT_{length}_{multiplier}'],\n", + " mode='markers',\n", + " name='SuperTrend Short',\n", + " line=dict(color=\"red\")),\n", + " row=1, col=1)\n", + "\n", + "fig" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "# To see the trades we will need to select a lower timeframe due the restrictions and speed of the plotly library\n", + "start_time = \"2023-11-03\"\n", + "end_time = \"2023-11-05\"\n", + "\n", + "processed_data_filtered = backtesting_results[\"processed_data\"][\n", + " (backtesting_results[\"processed_data\"][\"timestamp\"] >= start_time) &\n", + " (backtesting_results[\"processed_data\"][\"timestamp\"] <= end_time)\n", + "]\n", + "\n", + "executors_filtered = backtesting_results[\"executors_df\"][\n", + " (backtesting_results[\"executors_df\"][\"timestamp\"] >= start_time) &\n", + " (backtesting_results[\"executors_df\"][\"timestamp\"] <= end_time)\n", + "]" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "executors_filtered" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "\n", + "strategy_analysis = StrategyAnalysis(\n", + " positions=executors_filtered,\n", + " candles_df=processed_data_filtered,\n", + ")\n", + "\n", + "strategy_analysis.create_base_figure(volume=False, positions=True, trade_pnl=True)\n", + "fig = strategy_analysis.figure()\n", + "super_trend_long = strategy_analysis.candles_df[strategy_analysis.candles_df[f\"SUPERTd_{length}_{multiplier}\"] == 1]\n", + "super_trend_short = strategy_analysis.candles_df[strategy_analysis.candles_df[f\"SUPERTd_{length}_{multiplier}\"] == -1]\n", + "# Add the SuperTrend line\n", + "fig.add_trace(go.Scatter(x=super_trend_long.index, y=super_trend_long[f'SUPERT_{length}_{multiplier}'],\n", + " mode='markers',\n", + " name='SuperTrend Long',\n", + " line=dict(color=\"green\")),\n", + " row=1, col=1)\n", + "# Add the SuperTrend line\n", + "fig.add_trace(go.Scatter(x=super_trend_short.index, y=super_trend_short[f'SUPERT_{length}_{multiplier}'],\n", + " mode='markers',\n", + " name='SuperTrend Short',\n", + " line=dict(color=\"red\")),\n", + " row=1, col=1)\n", + "fig" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "markdown", + "source": [ + "### Scatter of PNL per Trade\n", + "This bar chart illustrates the PNL for each individual trade. Positive PNLs are shown in green and negative PNLs in red, providing a clear view of profitable vs. unprofitable trades.\n" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "import plotly.express as px\n", + "\n", + "executors_df = backtesting_results[\"executors_df\"]\n", + "\n", + "fig = px.scatter(executors_df, x=\"timestamp\", y='net_pnl_quote', title='PNL per Trade',\n", + " color='profitable', color_continuous_scale=['red', 'green'])\n", + "fig.show()" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "markdown", + "source": [ + "### Scatter Plot of Volume vs. PNL\n", + "This scatter plot explores the relationship between the trade volume and the PNL for each trade. It can reveal if larger volumes are associated with higher profits or losses.\n" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "fig = px.scatter(executors_df, x='volume', y='net_pnl_quote', title='Trade Volume vs. PNL')\n", + "fig.show()" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "markdown", + "source": [ + "### Histogram of PNL Distribution\n", + "The histogram displays the distribution of PNL values across all trades. It helps in understanding the frequency and range of profit and loss outcomes.\n" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "fig = px.histogram(executors_df, x='net_pnl_quote', title='PNL Distribution')\n", + "fig.show()\n" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "markdown", + "source": [ + "# Conclusion\n", + "We can see that the indicator has potential to bring good signals to trade and might be interesting to see how we can design a market maker that shifts the mid price based on this indicator.\n", + "A lot of the short signals are wrong but if we zoom in into the loss signals we can see that the losses are not that big and the wins are bigger and if we had implemented the trailing stop feature probably a lot of them are going to be profits." + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "markdown", + "source": [ + "# Next steps\n", + "- Filter only the loss signals and understand what you can do to prevent them\n", + "- Try different configuration values for the indicator\n", + "- Test in multiple markets, pick mature markets like BTC-USDT or ETH-USDT and also volatile markets like DOGE-USDT or SHIB-USDT" + ], + "metadata": { + "collapsed": false + } + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +}