{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# \"backtesting crypto hourly intervals\"\n", "> \"How to fetch and backtest crypto data using fastquant\"\n", "\n", "- toc: true\n", "- branch: master\n", "- badges: true\n", "- comments: true\n", "- author: Jerome de Leon (original), Mikee Jazmines (hourly)\n", "- categories: [crypto, backtest, grid search]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\"Open" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "# uncomment to install in colab\n", "# !pip3 install fastquant --update\n", "# or pip install git+https://www.github.com/enzoampil/fastquant.git@history" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## fetch data from binance" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### If a timestamp is given, it will return upto that timestamp" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from fastquant import get_crypto_data" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "crypto = get_crypto_data(\"BTC/USDT\", \n", " \"2020-11-01 00:00:00\", \n", " \"2021-11-30 05:00:00\",\n", " time_resolution='1h'\n", " )" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
openhighlowclosevolume
dt
2021-11-30 01:00:0057707.3857775.7057036.5457260.062307.00578
2021-11-30 02:00:0057260.0557512.4157215.1357357.851058.06476
2021-11-30 03:00:0057357.8657414.1156770.0157092.921577.44633
2021-11-30 04:00:0057092.9157277.3356983.9757205.00928.50736
2021-11-30 05:00:0057205.0057282.1156280.3756286.071646.18148
\n", "
" ], "text/plain": [ " open high low close volume\n", "dt \n", "2021-11-30 01:00:00 57707.38 57775.70 57036.54 57260.06 2307.00578\n", "2021-11-30 02:00:00 57260.05 57512.41 57215.13 57357.85 1058.06476\n", "2021-11-30 03:00:00 57357.86 57414.11 56770.01 57092.92 1577.44633\n", "2021-11-30 04:00:00 57092.91 57277.33 56983.97 57205.00 928.50736\n", "2021-11-30 05:00:00 57205.00 57282.11 56280.37 56286.07 1646.18148" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "crypto.tail()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### If no timestamp is given, it will return upto 00:00:00" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "crypto = get_crypto_data(\"BTC/USDT\", \n", " \"2020-11-01\", \n", " \"2021-11-30\",\n", " time_resolution='1h'\n", " )" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
openhighlowclosevolume
dt
2021-11-29 20:00:0057880.6758249.9957703.5758038.631210.67577
2021-11-29 21:00:0058034.2158400.0057987.8258247.181430.54047
2021-11-29 22:00:0058247.1758353.0158020.2558048.281303.51091
2021-11-29 23:00:0058048.2758099.6457667.0057776.251203.66000
2021-11-30 00:00:0057776.2557964.1857515.3857707.381404.65505
\n", "
" ], "text/plain": [ " open high low close volume\n", "dt \n", "2021-11-29 20:00:00 57880.67 58249.99 57703.57 58038.63 1210.67577\n", "2021-11-29 21:00:00 58034.21 58400.00 57987.82 58247.18 1430.54047\n", "2021-11-29 22:00:00 58247.17 58353.01 58020.25 58048.28 1303.51091\n", "2021-11-29 23:00:00 58048.27 58099.64 57667.00 57776.25 1203.66000\n", "2021-11-30 00:00:00 57776.25 57964.18 57515.38 57707.38 1404.65505" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "crypto.tail()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## run backtest with a grid of values" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "from fastquant import backtest\n", "\n", "results = backtest('smac', \n", " crypto, \n", " fast_period=[7,14,21,28], \n", " slow_period=[30,45,60,75],\n", " plot=False,\n", " verbose=False\n", " )" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "scrolled": true }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
strat_idinit_cashbuy_propsell_propfractionalcommissionstop_lossstop_trailtake_profitexecution_type...wonlostwon_avgwon_avg_prcntlost_avglost_avg_prcntwon_maxwon_max_prcntlost_maxlost_max_prcnt
0210000011False0000close...476711002.16297911.002163-4879.040000-4.87904044452.8644.45286-20958.42-20.95842
1010000011False0000close...801279914.9552509.914955-4755.445748-4.75544641070.1241.07012-14097.00-14.09700
2310000011False0000close...325615546.61062515.546611-5427.451607-5.42745252285.6652.28566-22280.58-22.28058
3110000011False0000close...569811869.05946411.869059-4863.816327-4.86381643645.7743.64577-20649.60-20.64960
41110000011False0000close...303713528.38533313.528385-7040.034324-7.04003441067.5441.06754-22302.12-22.30212
\n", "

5 rows × 44 columns

\n", "
" ], "text/plain": [ " strat_id init_cash buy_prop sell_prop fractional commission \\\n", "0 2 100000 1 1 False 0 \n", "1 0 100000 1 1 False 0 \n", "2 3 100000 1 1 False 0 \n", "3 1 100000 1 1 False 0 \n", "4 11 100000 1 1 False 0 \n", "\n", " stop_loss stop_trail take_profit execution_type ... won lost \\\n", "0 0 0 0 close ... 47 67 \n", "1 0 0 0 close ... 80 127 \n", "2 0 0 0 close ... 32 56 \n", "3 0 0 0 close ... 56 98 \n", "4 0 0 0 close ... 30 37 \n", "\n", " won_avg won_avg_prcnt lost_avg lost_avg_prcnt won_max \\\n", "0 11002.162979 11.002163 -4879.040000 -4.879040 44452.86 \n", "1 9914.955250 9.914955 -4755.445748 -4.755446 41070.12 \n", "2 15546.610625 15.546611 -5427.451607 -5.427452 52285.66 \n", "3 11869.059464 11.869059 -4863.816327 -4.863816 43645.77 \n", "4 13528.385333 13.528385 -7040.034324 -7.040034 41067.54 \n", "\n", " won_max_prcnt lost_max lost_max_prcnt \n", "0 44.45286 -20958.42 -20.95842 \n", "1 41.07012 -14097.00 -14.09700 \n", "2 52.28566 -22280.58 -22.28058 \n", "3 43.64577 -20649.60 -20.64960 \n", "4 41.06754 -22302.12 -22.30212 \n", "\n", "[5 rows x 44 columns]" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "results.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "That's a 258% maximum profit using only SMAC because bitcoin was bullish all time long!" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(7, 60)" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "#get best parameters on top row \n", "fast_best, slow_best = results.iloc[0][[\"fast_period\",\"slow_period\"]]\n", "fast_best, slow_best" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## run backtest using optimum values" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [], "source": [ "import matplotlib as pl\n", "pl.style.use(\"default\")\n", "pl.rcParams[\"figure.figsize\"] = (9,5)" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "application/javascript": [ "/* Put everything inside the global mpl namespace */\n", "window.mpl = {};\n", "\n", "\n", "mpl.get_websocket_type = function() {\n", " if (typeof(WebSocket) !== 'undefined') {\n", " return WebSocket;\n", " } else if (typeof(MozWebSocket) !== 'undefined') {\n", " return MozWebSocket;\n", " } else {\n", " alert('Your browser does not have WebSocket support. ' +\n", " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", " 'Firefox 4 and 5 are also supported but you ' +\n", " 'have to enable WebSockets in about:config.');\n", " };\n", "}\n", "\n", "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", " this.id = figure_id;\n", "\n", " this.ws = websocket;\n", "\n", " this.supports_binary = (this.ws.binaryType != undefined);\n", "\n", " if (!this.supports_binary) {\n", " var warnings = document.getElementById(\"mpl-warnings\");\n", " if (warnings) {\n", " warnings.style.display = 'block';\n", " warnings.textContent = (\n", " \"This browser does not support binary websocket messages. \" +\n", " \"Performance may be slow.\");\n", " }\n", " }\n", "\n", " this.imageObj = new Image();\n", "\n", " this.context = undefined;\n", " this.message = undefined;\n", " this.canvas = undefined;\n", " this.rubberband_canvas = undefined;\n", " this.rubberband_context = undefined;\n", " this.format_dropdown = undefined;\n", "\n", " this.image_mode = 'full';\n", "\n", " this.root = $('
');\n", " this._root_extra_style(this.root)\n", " this.root.attr('style', 'display: inline-block');\n", "\n", " $(parent_element).append(this.root);\n", "\n", " this._init_header(this);\n", " this._init_canvas(this);\n", " this._init_toolbar(this);\n", "\n", " var fig = this;\n", "\n", " this.waiting = false;\n", "\n", " this.ws.onopen = function () {\n", " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", " fig.send_message(\"send_image_mode\", {});\n", " if (mpl.ratio != 1) {\n", " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", " }\n", " fig.send_message(\"refresh\", {});\n", " }\n", "\n", " this.imageObj.onload = function() {\n", " if (fig.image_mode == 'full') {\n", " // Full images could contain transparency (where diff images\n", " // almost always do), so we need to clear the canvas so that\n", " // there is no ghosting.\n", " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", " }\n", " fig.context.drawImage(fig.imageObj, 0, 0);\n", " };\n", "\n", " this.imageObj.onunload = function() {\n", " fig.ws.close();\n", " }\n", "\n", " this.ws.onmessage = this._make_on_message_function(this);\n", "\n", " this.ondownload = ondownload;\n", "}\n", "\n", "mpl.figure.prototype._init_header = function() {\n", " var titlebar = $(\n", " '
');\n", " var titletext = $(\n", " '
');\n", " titlebar.append(titletext)\n", " this.root.append(titlebar);\n", " this.header = titletext[0];\n", "}\n", "\n", "\n", "\n", "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", "\n", "}\n", "\n", "\n", "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", "\n", "}\n", "\n", "mpl.figure.prototype._init_canvas = function() {\n", " var fig = this;\n", "\n", " var canvas_div = $('
');\n", "\n", " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", "\n", " function canvas_keyboard_event(event) {\n", " return fig.key_event(event, event['data']);\n", " }\n", "\n", " canvas_div.keydown('key_press', canvas_keyboard_event);\n", " canvas_div.keyup('key_release', canvas_keyboard_event);\n", " this.canvas_div = canvas_div\n", " this._canvas_extra_style(canvas_div)\n", " this.root.append(canvas_div);\n", "\n", " var canvas = $('');\n", " canvas.addClass('mpl-canvas');\n", " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", "\n", " this.canvas = canvas[0];\n", " this.context = canvas[0].getContext(\"2d\");\n", "\n", " var backingStore = this.context.backingStorePixelRatio ||\n", "\tthis.context.webkitBackingStorePixelRatio ||\n", "\tthis.context.mozBackingStorePixelRatio ||\n", "\tthis.context.msBackingStorePixelRatio ||\n", "\tthis.context.oBackingStorePixelRatio ||\n", "\tthis.context.backingStorePixelRatio || 1;\n", "\n", " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", "\n", " var rubberband = $('');\n", " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", "\n", " var pass_mouse_events = true;\n", "\n", " canvas_div.resizable({\n", " start: function(event, ui) {\n", " pass_mouse_events = false;\n", " },\n", " resize: function(event, ui) {\n", " fig.request_resize(ui.size.width, ui.size.height);\n", " },\n", " stop: function(event, ui) {\n", " pass_mouse_events = true;\n", " fig.request_resize(ui.size.width, ui.size.height);\n", " },\n", " });\n", "\n", " function mouse_event_fn(event) {\n", " if (pass_mouse_events)\n", " return fig.mouse_event(event, event['data']);\n", " }\n", "\n", " rubberband.mousedown('button_press', mouse_event_fn);\n", " rubberband.mouseup('button_release', mouse_event_fn);\n", " // Throttle sequential mouse events to 1 every 20ms.\n", " rubberband.mousemove('motion_notify', mouse_event_fn);\n", "\n", " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", "\n", " canvas_div.on(\"wheel\", function (event) {\n", " event = event.originalEvent;\n", " event['data'] = 'scroll'\n", " if (event.deltaY < 0) {\n", " event.step = 1;\n", " } else {\n", " event.step = -1;\n", " }\n", " mouse_event_fn(event);\n", " });\n", "\n", " canvas_div.append(canvas);\n", " canvas_div.append(rubberband);\n", "\n", " this.rubberband = rubberband;\n", " this.rubberband_canvas = rubberband[0];\n", " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", " this.rubberband_context.strokeStyle = \"#000000\";\n", "\n", " this._resize_canvas = function(width, height) {\n", " // Keep the size of the canvas, canvas container, and rubber band\n", " // canvas in synch.\n", " canvas_div.css('width', width)\n", " canvas_div.css('height', height)\n", "\n", " canvas.attr('width', width * mpl.ratio);\n", " canvas.attr('height', height * mpl.ratio);\n", " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", "\n", " rubberband.attr('width', width);\n", " rubberband.attr('height', height);\n", " }\n", "\n", " // Set the figure to an initial 600x600px, this will subsequently be updated\n", " // upon first draw.\n", " this._resize_canvas(600, 600);\n", "\n", " // Disable right mouse context menu.\n", " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", " return false;\n", " });\n", "\n", " function set_focus () {\n", " canvas.focus();\n", " canvas_div.focus();\n", " }\n", "\n", " window.setTimeout(set_focus, 100);\n", "}\n", "\n", "mpl.figure.prototype._init_toolbar = function() {\n", " var fig = this;\n", "\n", " var nav_element = $('
');\n", " nav_element.attr('style', 'width: 100%');\n", " this.root.append(nav_element);\n", "\n", " // Define a callback function for later on.\n", " function toolbar_event(event) {\n", " return fig.toolbar_button_onclick(event['data']);\n", " }\n", " function toolbar_mouse_event(event) {\n", " return fig.toolbar_button_onmouseover(event['data']);\n", " }\n", "\n", " for(var toolbar_ind in mpl.toolbar_items) {\n", " var name = mpl.toolbar_items[toolbar_ind][0];\n", " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", " var image = mpl.toolbar_items[toolbar_ind][2];\n", " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", "\n", " if (!name) {\n", " // put a spacer in here.\n", " continue;\n", " }\n", " var button = $('