{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## Trend Following System 1\n", "Most of the following imports are mainstream, well known Python libraries.\n", "\n", "alpha_vantage and fix_yahoo are specialist libraries to download stock data free. To use alpha_vantage you will need to obtain your own key from the providers https://www.alphavantage.co/\n", "\n", "ffn is a specialist library to report trading systems statistics" ] }, { "cell_type": "code", "execution_count": 80, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import matplotlib.pyplot as plt\n", "from matplotlib.pyplot import figure\n", "%matplotlib notebook\n", "import pandas as pd\n", "import datetime as datetime\n", "from alpha_vantage.timeseries import TimeSeries\n", "import fix_yahoo_finance as yf\n", "#because the is_list_like is moved to pandas.api.types\n", "pd.core.common.is_list_like = pd.api.types.is_list_like\n", "import ffn\n", "pd.set_option(\"max_colwidth\", 100)\n", "pd.set_option(\"display.max_rows\", 100000)\n", "pd.set_option(\"display.max_columns\", 1000)\n", "#import pixiedust" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The following two cells can be used to download stock data and then blanked out again once the data has been saved to csv.\n", "\n", "The following line is to create split adjusted Open prices since only the adjusted close is provided:\n", "\n", "data['Adj_Open']=data.Open*(data.Adj_Close/data.Close)" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "#ts = TimeSeries(key='insert own key', output_format='pandas')\n", "#data, meta_data = ts.get_daily_adjusted(symbol='SPY', outputsize='full')\n", "#data = yf.download(\"SPY\", start=\"1970-01-01\", end=\"2018-12-08\")" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "#data.rename(columns={'Adj Close': 'Adj_Close'}, inplace=True)\n", "#data['Adj_Open']=data.Open*(data.Adj_Close/data.Close)\n", "#data.to_csv('../data/Stocks/spy.csv')\n", "#data.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Chart the data to check there are no obvious problems with the split adjusted data." ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "scrolled": true }, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "pricing = pd.read_csv(\n", " '../data/Stocks/spy.csv',\n", " header=0,\n", " parse_dates=[\"Date\"],\n", " #index_col=0,\n", " usecols=['Date','Adj_Open', 'Adj_Close'])\n", "figure(num=None, figsize=(6, 4), dpi=80, facecolor='w', edgecolor='k')\n", "plt.plot(pricing.Adj_Open, label='Spy Open')\n", "plt.plot(pricing.Adj_Close, label='Spy Close')\n", "plt.xlabel('Date')\n", "plt.ylabel('Price')\n", "plt.legend()" ] }, { "cell_type": "code", "execution_count": 5, "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", "
DateAdj_CloseAdj_Open
01993-01-2927.11225327.131505
11993-02-0127.30508227.131502
21993-02-0227.36290427.285771
31993-02-0327.65218527.401472
41993-02-0427.76789327.748579
\n", "
" ], "text/plain": [ " Date Adj_Close Adj_Open\n", "0 1993-01-29 27.112253 27.131505\n", "1 1993-02-01 27.305082 27.131502\n", "2 1993-02-02 27.362904 27.285771\n", "3 1993-02-03 27.652185 27.401472\n", "4 1993-02-04 27.767893 27.748579" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "pricing.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Shift the Adjusted Close down one row so that it aligns with the next day's Adjusted Open. That way we can calculate the signals using the previous day's Close and enter the trade on the next day's Open" ] }, { "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", "
DateAdj_CloseAdj_Open
65082018-11-30273.980011273.809998
65092018-12-03275.649994280.279999
65102018-12-04279.299988278.369995
65112018-12-06270.250000265.920013
65122018-12-07269.839996269.459991
\n", "
" ], "text/plain": [ " Date Adj_Close Adj_Open\n", "6508 2018-11-30 273.980011 273.809998\n", "6509 2018-12-03 275.649994 280.279999\n", "6510 2018-12-04 279.299988 278.369995\n", "6511 2018-12-06 270.250000 265.920013\n", "6512 2018-12-07 269.839996 269.459991" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "stock=pricing.copy()\n", "stock.Adj_Close=stock.Adj_Close.shift(1)\n", "stock.tail()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Create three or more separate blocks of data for testing. Reserve one of these for out of sample testing." ] }, { "cell_type": "code", "execution_count": 37, "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", "
DateAdj_CloseAdj_Open
02001-09-0481.72002481.505194
12001-09-0581.19735781.397842
22001-09-0681.39784280.646166
32001-09-0779.30027078.763331
42001-09-1077.83266477.102443
\n", "
" ], "text/plain": [ " Date Adj_Close Adj_Open\n", "0 2001-09-04 81.720024 81.505194\n", "1 2001-09-05 81.197357 81.397842\n", "2 2001-09-06 81.397842 80.646166\n", "3 2001-09-07 79.300270 78.763331\n", "4 2001-09-10 77.832664 77.102443" ] }, "execution_count": 37, "metadata": {}, "output_type": "execute_result" } ], "source": [ "stock_1=stock.iloc[0:2170].copy()\n", "stock_2=stock.iloc[2170:4342].copy().reset_index(drop=True)\n", "stock_3=stock.iloc[4342:6513].copy().reset_index(drop=True) \n", "stock_2.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here is the code for the actual function which loops through the stock data and executes the trades. You can experiment with different parameters such as changing the maximum position size or starting capital." ] }, { "cell_type": "code", "execution_count": 358, "metadata": { "pixiedust": { "displayParams": {} } }, "outputs": [], "source": [ "# Trade using a simple trend following strategy\n", "def trade(stock, length):\n", "\n", " temp_dict = {}\n", " # If window length is 0, algorithm doesn't make sense, so exit\n", " if length == 0:\n", " return 0\n", "\n", " # Compute rolling mean and rolling standard deviation\n", " rolling_window = stock.Adj_Close.rolling(window=length)\n", " mu = rolling_window.mean()\n", " std = rolling_window.std()\n", "\n", " #If you don't use a maximum position size the positions will keep on pyramidding.\n", " #Set max_position to a high number (1000?) to disable this parameter\n", " max_position =1\n", " \n", " #Slippage and commission adjustment - simply reduces equity by a percentage guess\n", " slippage_adj=0.99\n", "\n", " # Compute the z-scores for each day using the historical data up to that day\n", " zscores = (stock.Adj_Close - mu) / std\n", "\n", " # Simulate trading\n", " # Start with your chosen starting capital and no positions\n", " money = 30\n", " position_count = 0\n", "\n", " for i, row in enumerate(stock.itertuples(),0):\n", "\n", " # Sell short if the z-score is > 1\n", " if zscores[i] > 1 and position_count < max_position:\n", " money -= stock.Adj_Open[i] *(1/slippage_adj)\n", " position_count += 1\n", " # Buy long if the z-score is < 1\n", " elif zscores[i] < -1 and position_count > max_position * -1:\n", " # print (position_count)\n", " money += stock.Adj_Open[i] *slippage_adj\n", " position_count -= 1\n", " # Clear positions if the z-score between -.5 and .5\n", " elif abs(zscores[i]) < 0.5:\n", " if position_count > 0: \n", " money += position_count * stock.Adj_Open[i]*slippage_adj \n", " elif position_count < 0: \n", " money += position_count * stock.Adj_Open[i]*(1/slippage_adj) \n", " position_count = 0\n", " #fill dictionary with the trading results.\n", " temp_dict[stock.Date[i]] = [\n", " stock.Adj_Open[i], stock.Adj_Close[i], mu[i], std[i], zscores[i],\n", " money, position_count,stock.Adj_Open[i] *(1/slippage_adj),stock.Adj_Open[i] *slippage_adj\n", " ]\n", " #create a dataframe to return for use in calculating and charting the trading results\n", " pr = pd.DataFrame(data=temp_dict).T\n", " pr.index.name = 'Date'\n", " pr.index = pd.to_datetime(pr.index)\n", " pr.columns = [\n", " 'Open', 'Close', 'mu', 'std', 'zscores', 'money', 'position_count','buy_slippage','sell_slippage'\n", " ]\n", " pr['equity'] = pr.money + (pr.Open * pr.position_count)\n", " #\n", " return pr" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The next cell calls the function. Experiment with different moving averages by altering the number in brackets." ] }, { "cell_type": "code", "execution_count": 359, "metadata": { "pixiedust": { "displayParams": {} } }, "outputs": [], "source": [ "moving_average=200\n", "profit = trade(pricing, moving_average)\n", "profit.to_csv('../data/profit.csv')" ] }, { "cell_type": "code", "execution_count": 360, "metadata": {}, "outputs": [], "source": [ "#copy_profit = pd.read_csv('../data/profit.csv',header=0,parse_dates=[\"Date\"],)\n", "#copy_profit.head(10)" ] }, { "cell_type": "code", "execution_count": 361, "metadata": {}, "outputs": [], "source": [ "series=profit[['equity']].copy()" ] }, { "cell_type": "code", "execution_count": 362, "metadata": {}, "outputs": [], "source": [ "series=series[moving_average:-1]\n", "#series.head()" ] }, { "cell_type": "code", "execution_count": 363, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Stat equity\n", "------------------- ----------\n", "Start 1993-11-12\n", "End 2018-12-06\n", "Risk-free rate 0.00%\n", "\n", "Total Return 570.57%\n", "Daily Sharpe 0.51\n", "Daily Sortino 0.80\n", "CAGR 7.89%\n", "Max Drawdown -41.74%\n", "Calmar Ratio 0.19\n", "\n", "MTD 0.00%\n", "3m -9.45%\n", "6m -3.04%\n", "YTD -5.11%\n", "1Y -2.08%\n", "3Y (ann.) 8.53%\n", "5Y (ann.) 5.32%\n", "10Y (ann.) 5.59%\n", "Since Incep. (ann.) 7.89%\n", "\n", "Daily Sharpe 0.51\n", "Daily Sortino 0.80\n", "Daily Mean (ann.) 9.27%\n", "Daily Vol (ann.) 18.25%\n", "Daily Skew 0.03\n", "Daily Kurt 7.84\n", "Best Day 9.72%\n", "Worst Day -8.86%\n", "\n", "Monthly Sharpe 0.58\n", "Monthly Sortino 1.10\n", "Monthly Mean (ann.) 8.77%\n", "Monthly Vol (ann.) 15.21%\n", "Monthly Skew 0.26\n", "Monthly Kurt 1.19\n", "Best Month 16.21%\n", "Worst Month -12.82%\n", "\n", "Yearly Sharpe 0.51\n", "Yearly Sortino 1.39\n", "Yearly Mean 9.34%\n", "Yearly Vol 18.30%\n", "Yearly Skew 0.06\n", "Yearly Kurt -0.35\n", "Best Year 44.26%\n", "Worst Year -30.42%\n", "\n", "Avg. Drawdown -2.92%\n", "Avg. Drawdown Days 47.78\n", "Avg. Up Month 3.62%\n", "Avg. Down Month -2.97%\n", "Win Year % 60.00%\n", "Win 12m % 70.45%\n" ] } ], "source": [ "stats = series.calc_stats()\n", "stats.display()" ] }, { "cell_type": "code", "execution_count": 364, "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 = $('');\n", " button.click(method_name, toolbar_event);\n", " button.mouseover(tooltip, toolbar_mouse_event);\n", " nav_element.append(button);\n", " }\n", "\n", " // Add the status bar.\n", " var status_bar = $('');\n", " nav_element.append(status_bar);\n", " this.message = status_bar[0];\n", "\n", " // Add the close button to the window.\n", " var buttongrp = $('
');\n", " var button = $('');\n", " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", " buttongrp.append(button);\n", " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", " titlebar.prepend(buttongrp);\n", "}\n", "\n", "mpl.figure.prototype._root_extra_style = function(el){\n", " var fig = this\n", " el.on(\"remove\", function(){\n", "\tfig.close_ws(fig, {});\n", " });\n", "}\n", "\n", "mpl.figure.prototype._canvas_extra_style = function(el){\n", " // this is important to make the div 'focusable\n", " el.attr('tabindex', 0)\n", " // reach out to IPython and tell the keyboard manager to turn it's self\n", " // off when our div gets focus\n", "\n", " // location in version 3\n", " if (IPython.notebook.keyboard_manager) {\n", " IPython.notebook.keyboard_manager.register_events(el);\n", " }\n", " else {\n", " // location in version 2\n", " IPython.keyboard_manager.register_events(el);\n", " }\n", "\n", "}\n", "\n", "mpl.figure.prototype._key_event_extra = function(event, name) {\n", " var manager = IPython.notebook.keyboard_manager;\n", " if (!manager)\n", " manager = IPython.keyboard_manager;\n", "\n", " // Check for shift+enter\n", " if (event.shiftKey && event.which == 13) {\n", " this.canvas_div.blur();\n", " event.shiftKey = false;\n", " // Send a \"J\" for go to next cell\n", " event.which = 74;\n", " event.keyCode = 74;\n", " manager.command_mode();\n", " manager.handle_keydown(event);\n", " }\n", "}\n", "\n", "mpl.figure.prototype.handle_save = function(fig, msg) {\n", " fig.ondownload(fig, null);\n", "}\n", "\n", "\n", "mpl.find_output_cell = function(html_output) {\n", " // Return the cell and output element which can be found *uniquely* in the notebook.\n", " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", " // IPython event is triggered only after the cells have been serialised, which for\n", " // our purposes (turning an active figure into a static one), is too late.\n", " var cells = IPython.notebook.get_cells();\n", " var ncells = cells.length;\n", " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", " data = data.data;\n", " }\n", " if (data['text/html'] == html_output) {\n", " return [cell, data, j];\n", " }\n", " }\n", " }\n", " }\n", "}\n", "\n", "// Register the function which deals with the matplotlib target/channel.\n", "// The kernel may be null if the page has been refreshed.\n", "if (IPython.notebook.kernel != null) {\n", " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", "}\n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "ax = prices.plot(logy=True)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "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.6.5" } }, "nbformat": 4, "nbformat_minor": 2 }