{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "### 0. Imports\n", "---\n", " - Pablo Leo Muñoz - pleo@etsfactory.com" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# data wrangling\n", "import numpy as np\n", "import pandas as pd\n", "\n", "# plots\n", "import matplotlib.pyplot as plt\n", "from matplotlib import animation, rc\n", "from matplotlib.cm import get_cmap\n", "from mpl_toolkits.mplot3d import Axes3D\n", "from matplotlib.font_manager import FontProperties\n", "from matplotlib.collections import LineCollection\n", "from matplotlib.colors import ListedColormap\n", "from mpl_toolkits.mplot3d.art3d import Line3DCollection\n", "\n", "# set the type of animations\n", "rc('animation', html='html5')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 1. Data load\n", "---" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# ------------------- ADD INDEX DATA BELOW! ------------------------\n", "# generate RANDOM prices (one for each index)\n", "index_returns = np.random.normal(loc=1e-4, scale=5e-3, size=(783, 9))\n", "index_returns = np.vstack((np.zeros(shape=(1, 9)) + 100, index_returns))\n", "\n", "# generate the cummulative returns\n", "index_prices = np.cumprod(1 + index_returns, axis=0)\n", "\n", "# select the window\n", "window = 261\n", "indexes_rolling = np.zeros(shape=(index_prices.shape[0]-window, 9))\n", "\n", "# generate the rolling equity\n", "for i in range(window, index_prices.shape[0], 1):\n", " indexes_rolling[i-window] = (index_prices[i]/index_prices[i-window]) - 1\n", "\n", "# generate dataframe with the data\n", "index = pd.date_range('2019-01-01', periods=index_prices.shape[0]-window, freq='B')\n", "columns = ['GER | DAX', 'UK | UKX', 'FR | CAC', 'IT | FTSEMIB', 'ES | IBEX', 'NL | AEX', 'CH | SMI', 'SE | OMX', 'BE | BEL20']\n", "indexes_rolling = pd.DataFrame(indexes_rolling, index=index, columns=columns)\n", "# ------------------- ADD INDEX DATA ABOVE! ------------------------" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 2. 2D Animation\n", "---" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# create the figure\n", "fig, axes = plt.subplots(1, 2, figsize=(9, 3.6), gridspec_kw={'width_ratios': [.9, .1]})\n", "fig.patch.set_alpha(1)\n", "\n", "# make right plot invisible and only update left one\n", "axes[1].axis('off')\n", "ax = axes[0]\n", "\n", "# get the cmap to use\n", "cmap = get_cmap('RdYlGn')\n", "\n", "# get the slice\n", "current_slice = indexes_rolling.values[:261, :]\n", "index_names = indexes_rolling.columns\n", "index_dates = indexes_rolling.index\n", "\n", "# list holding the lines\n", "lines = []\n", "\n", "# for each index...\n", "for i in range(current_slice.shape[1]):\n", "\n", " # get the coordinates\n", " x = np.array(np.arange(current_slice.shape[0]))\n", " y = np.array(current_slice[:, i])\n", "\n", " # crete points and segments to color\n", " points = np.array([x, y]).T.reshape(-1, 1, 2)\n", " segments = np.concatenate([points[:-1], points[1:]], axis=1)\n", "\n", " # Create a continuous norm to map from data points to colors\n", " norm = plt.Normalize(-0.19, 0.19)\n", " lc = LineCollection(segments, cmap=cmap, norm=norm)\n", "\n", " # Set the values used for colormapping\n", " lc.set_array(y)\n", " lc.set_linewidth(2)\n", " lc.set_color(cmap(y[-1] * 2.5 + 0.5))\n", " lc.set_label(index_names[i])\n", " lines.append(ax.add_collection(lc))\n", "\n", "# add the grids\n", "ax.legend(loc='center right', bbox_to_anchor=(1.32, 0.5), fancybox=True, facecolor=(.95,.95,.95,1), framealpha=1, shadow=False, frameon=True, ncol=1, columnspacing=0, prop={'family': 'DejaVu Sans Mono'})\n", "ax.yaxis.grid(color='gray', linestyle='dashed')\n", "ax.xaxis.grid(color='gray', linestyle='dashed')\n", "ax.set_xlim(0, current_slice.shape[0]-1)\n", "ax.set_ylim(-0.39, 0.39)\n", "ax.set_yticklabels(['{:.0%}'.format(val) for val in ax.get_yticks()])\n", "ax.set_ylabel('Rolling Equity 1Y')\n", "ax.set_xlabel('Date')\n", "ax.set_xticklabels([index_dates[int(val)].strftime('%m/%y') for val in ax.get_xticks()])\n", "ax.set_facecolor((0, 0, 0, 1.0))\n", "\n", "# show the plot\n", "plt.show()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def update_lines_2D(num, data, columns, dates, cmap, lines, ax):\n", " '''\n", " Function that updates the lines of a plot in 2D\n", " '''\n", " \n", " # get the slice\n", " current_slice = data[num:261+num, :]\n", " current_dates = dates[num:261+num]\n", " \n", " # for each index...\n", " for i in range(current_slice.shape[1]):\n", "\n", " # get the coordinates\n", " x = np.array(np.arange(current_slice.shape[0]))\n", " y = np.array(current_slice[:, i])\n", "\n", " # crete points and segments to color\n", " points = np.array([x, y]).T.reshape(-1, 1, 2)\n", " segments = np.concatenate([points[:-1], points[1:]], axis=1)\n", "\n", " # Create a continuous norm to map from data points to colors\n", " norm = plt.Normalize(-0.22, 0.22) \n", " lines[i].set_segments(segments)\n", " lines[i].set_array(y)\n", " lines[i].set_color(cmap(y[-1] * 2.5 + 0.5))\n", " \n", " # update the ticks and labels\n", " ax.set_xticklabels([dates[int(val)+num].strftime('%m/%y') for val in ax.get_xticks()[:-1]] + [''])\n", " ax.legend(loc='center right', bbox_to_anchor=(1.32, 0.5), fancybox=True, facecolor=(.95,.95,.95,1), framealpha=1, shadow=False, frameon=True, ncol=1, columnspacing=0, prop={'family': 'DejaVu Sans Mono'})\n", " \n", " # return the lines\n", " return lines\n", "\n", "\n", "def init_lines_2D():\n", " '''\n", " Function that initiates the lines of a plot in 2D\n", " '''\n", " for line in lines:\n", " line.set_array([])\n", " return lines" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# create the animation\n", "line_ani = animation.FuncAnimation(fig=fig, \n", " func=update_lines_2D, \n", " # frames=30,\n", " frames=indexes_rolling.shape[0]-261, \n", " init_func=init_lines_2D, \n", " fargs=(indexes_rolling.values, indexes_rolling.columns, indexes_rolling.index, cmap, lines, ax),\n", " interval=75, \n", " blit=True)\n", "\n", "# show the animation\n", "line_ani" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# # callback to show the evolution\n", "# progress_callback = lambda i, n: print('Saving frame {:.0%}'.format(i/n)) if int((i/n) * 100) % 10 == 0 else None\n", "\n", "# # save the animation\n", "# line_ani.save('./2D_animation.gif', writer='imagemagick', fps=14, dpi=80, codec='h264', bitrate=2048, progress_callback=progress_callback)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 3.1 3D Animation\n", "---" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# create the figure\n", "fig = plt.figure(figsize=(14.4, 9))\n", "ax = fig.add_subplot(111, projection='3d')\n", "fig.patch.set_alpha(1)\n", "\n", "# get the cmap to use\n", "cmap = get_cmap('RdYlGn')\n", "\n", "# get the slice\n", "current_slice = indexes_rolling.values[:261, :]\n", "index_names = indexes_rolling.columns\n", "index_dates = indexes_rolling.index\n", "\n", "# list holding the lines\n", "lines = []\n", "\n", "# for each index...\n", "for i in range(current_slice.shape[1]):\n", "\n", " # get the coordinates\n", " x = np.array(np.arange(current_slice.shape[0]))\n", " y = np.tile(i, current_slice.shape[0])\n", " z = np.array(current_slice[:, i])\n", "\n", " # crete points and segments to color\n", " points = np.array([x, y, z]).T.reshape(-1, 1, 3)\n", " segments = np.concatenate([points[:-1], points[1:]], axis=1)\n", "\n", " # Create a continuous norm to map from data points to colors\n", " norm = plt.Normalize(-0.19, 0.19)\n", " lc = Line3DCollection(segments, cmap=cmap, norm=norm, zorder=current_slice.shape[1]-i)\n", "\n", " # Set the values used for colormapping\n", " lc.set_array(z)\n", " lc.set_linewidth(2)\n", " lc.set_color(cmap(z[-1] * 2.5 + 0.5))\n", " lc.set_label(index_names[i])\n", " lines.append(ax.add_collection(lc))\n", "\n", "# add the grids\n", "ax.legend(loc='center right', bbox_to_anchor=(1.1, 0.46), fancybox=True, facecolor=(.95,.95,.95,1), framealpha=1, shadow=False, frameon=True, ncol=1, columnspacing=0, prop={'family': 'DejaVu Sans Mono'})\n", "ax.set_zlabel('Rolling Equity 1Y', labelpad=10)\n", "ax.set_zlim(-0.39, 0.39)\n", "ax.set_zticklabels([' '* 3 + '{:.0%}'.format(val) for val in ax.get_zticks()], fontdict={'verticalalignment': 'center', 'horizontalalignment': 'center'})\n", "ax.set_xlabel('Date', labelpad=30)\n", "ax.set_xlim(0, current_slice.shape[0]-1)\n", "ax.set_xticklabels([index_dates[int(val)].strftime('%m/%y') for val in ax.get_xticks()[:-1]] + [''], rotation=0, fontdict={'verticalalignment': 'top', 'horizontalalignment': 'center'})\n", "ax.set_yticks(np.arange(current_slice.shape[1]))\n", "ax.set_yticklabels([index_names[i] for i in range(current_slice.shape[1])], rotation=-15, fontdict={'verticalalignment': 'center', 'horizontalalignment': 'left'})\n", "ax.w_xaxis.set_pane_color((0, 0, 0, 1.0))\n", "ax.w_yaxis.set_pane_color((0, 0, 0, 1.0))\n", "ax.w_zaxis.set_pane_color((0, 0, 0, 1.0))\n", "ax.view_init(25, -60)\n", "\n", "# ------------------------------------------------------------------\n", "x_scale=1.8\n", "y_scale=1\n", "z_scale=1\n", "\n", "scale=np.diag([x_scale, y_scale, z_scale, 1.0])\n", "scale=scale*(1.0/scale.max())\n", "scale[3,3]=1.0\n", "\n", "def short_proj():\n", " return np.dot(Axes3D.get_proj(ax), scale)\n", "\n", "ax.get_proj=short_proj\n", "\n", "fig.subplots_adjust(left=0, right=1, bottom=0, top=1)\n", "# ------------------------------------------------------------------\n", "\n", "# show the plot\n", "plt.show()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def update_lines_3D(num, data, columns, dates, cmap, lines, ax):\n", " '''\n", " Function that updates the lines of a plot in 2D\n", " '''\n", " \n", " # get the slice\n", " current_slice = data[num:261+num, :]\n", " current_dates = dates[num:261+num]\n", " \n", " # for each index...\n", " for i in range(current_slice.shape[1]):\n", "\n", " # get the coordinates\n", " x = np.arange(current_slice.shape[0])\n", " y = np.tile(i, current_slice.shape[0])\n", " z = np.array(current_slice[:, i])\n", "\n", " # crete points and segments to color\n", " points = np.array([x, y, z]).T.reshape(-1, 1, 3)\n", " segments = np.concatenate([points[:-1], points[1:]], axis=1)\n", "\n", " # Create a continuous norm to map from data points to colors\n", " norm = plt.Normalize(-0.19, 0.19) \n", " lines[i].set_segments(segments)\n", " lines[i].set_array(z)\n", " lines[i].set_color(cmap(z[-1] * 2.5 + 0.5))\n", "\n", " # update the ticks and labels\n", " ax.set_xticklabels([dates[int(val)+num].strftime('%m/%y') for val in ax.get_xticks()[:-1]] + [''], rotation=0, fontdict={'verticalalignment': 'top', 'horizontalalignment': 'center'})\n", " ax.legend(loc='center right', bbox_to_anchor=(1.1, 0.46), fancybox=True, facecolor=(.95,.95,.95,1), framealpha=1, shadow=False, frameon=True, ncol=1, columnspacing=0, prop={'family': 'DejaVu Sans Mono'})\n", " \n", " # return the lines\n", " return lines\n", "\n", "def init_lines_3D():\n", " for line in lines:\n", " line.set_array([])\n", " return lines" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# create the animation\n", "line_ani = animation.FuncAnimation(fig=fig, \n", " func=update_lines_3D, \n", " # frames=30,\n", " frames=indexes_rolling.shape[0]-261, \n", " init_func=init_lines_3D, \n", " fargs=(indexes_rolling.values, indexes_rolling.columns, indexes_rolling.index, cmap, lines, ax),\n", " interval=75, \n", " blit=True)\n", "\n", "# show the animation\n", "line_ani" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# # callback to show the evolution\n", "# progress_callback = lambda i, n: print('Saving frame {:.0%}'.format(i/n)) if int((i/n) * 100) % 10 == 0 else None\n", "\n", "# # save the animation\n", "# line_ani.save('./3D_animation.gif', writer='imagemagick', fps=14, dpi=80, codec='h264', bitrate=2048, progress_callback=progress_callback)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 3.2 3D Mesh Animation\n", "---" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# create the figure\n", "fig = plt.figure(figsize=(14.4, 9))\n", "ax = fig.add_subplot(111, projection='3d')\n", "fig.patch.set_alpha(1)\n", "\n", "# get the cmap to use\n", "cmap = get_cmap('RdYlGn')\n", "\n", "# get the slice\n", "# current_slice = indexes_rolling.values[:261, :]\n", "current_slice = indexes_rolling.values[:int(261/2), :]\n", "index_names = indexes_rolling.columns\n", "index_dates = indexes_rolling.index\n", "\n", "# list holding the lines\n", "lines = []\n", "\n", "# for each index...\n", "for i in range(current_slice.shape[1]):\n", "\n", " # get the coordinates\n", " x = np.array(np.arange(current_slice.shape[0]))\n", " y = np.tile(i, current_slice.shape[0])\n", " z = np.array(current_slice[:, i])\n", "\n", " # crete points and segments to color\n", " points = np.array([x, y, z]).T.reshape(-1, 1, 3)\n", " segments = np.concatenate([points[:-1], points[1:]], axis=1)\n", "\n", " # Create a continuous norm to map from data points to colors\n", " norm = plt.Normalize(-0.19, 0.19)\n", " lc = Line3DCollection(segments, cmap=cmap, norm=norm, zorder=current_slice.shape[1]-i)\n", "\n", " # Set the values used for colormapping\n", " lc.set_array(z)\n", " lc.set_linewidth(2)\n", " lc.set_color(cmap(z[-1] * 2.5 + 0.5))\n", " lc.set_label(index_names[i])\n", " lines.append(ax.add_collection(lc))\n", "\n", "# list holding the mesh lines\n", "mesh_lines = []\n", "\n", "# for each day...\n", "for j in range(current_slice.shape[0]):\n", "\n", " if j % 1 == 0:\n", " \n", " # get the coordinates\n", " x = np.tile(j, current_slice.shape[1])\n", " y = np.arange(current_slice.shape[1])\n", " z = np.array(current_slice[j, :])\n", "\n", " # crete points and segments to color\n", " points = np.array([x, y, z]).T.reshape(-1, 1, 3)\n", " segments = np.concatenate([points[:-1], points[1:]], axis=1)\n", "\n", " # Create a continuous norm to map from data points to colors\n", " norm = plt.Normalize(-0.19, 0.19)\n", " lc = Line3DCollection(segments, cmap=cmap, norm=norm)\n", "\n", " # Set the values used for colormapping\n", " lc.set_array(z)\n", " lc.set_linewidth(2)\n", " mesh_lines.append(ax.add_collection(lc))\n", " \n", "# add the grids\n", "ax.legend(loc='center right', bbox_to_anchor=(1.1, 0.46), fancybox=True, facecolor=(.95,.95,.95,1), framealpha=1, shadow=False, frameon=True, ncol=1, columnspacing=0, prop={'family': 'DejaVu Sans Mono'})\n", "ax.set_zlabel('Rolling Equity 1Y', labelpad=10)\n", "ax.set_zlim(-0.39, 0.39)\n", "ax.set_zticklabels([' '* 3 + '{:.0%}'.format(val) for val in ax.get_zticks()], fontdict={'verticalalignment': 'center', 'horizontalalignment': 'center'})\n", "ax.set_xlabel('Date', labelpad=30)\n", "ax.set_xlim(0, current_slice.shape[0]-1)\n", "ax.set_xticklabels([index_dates[int(val)].strftime('%m/%y') for val in ax.get_xticks()[:-1]] + [''], rotation=0, fontdict={'verticalalignment': 'top', 'horizontalalignment': 'center'})\n", "ax.set_yticks(np.arange(current_slice.shape[1]))\n", "ax.set_yticklabels([index_names[i]for i in range(current_slice.shape[1])], rotation=-15, fontdict={'verticalalignment': 'center', 'horizontalalignment': 'left'})\n", "ax.w_xaxis.set_pane_color((0, 0, 0, 1.0))\n", "ax.w_yaxis.set_pane_color((0, 0, 0, 1.0))\n", "ax.w_zaxis.set_pane_color((0, 0, 0, 1.0))\n", "ax.view_init(25, -60)\n", "\n", "# ------------------------------------------------------------------\n", "x_scale=1.8\n", "y_scale=1\n", "z_scale=1\n", "\n", "scale=np.diag([x_scale, y_scale, z_scale, 1.0])\n", "scale=scale*(1.0/scale.max())\n", "scale[3,3]=1.0\n", "\n", "def short_proj():\n", " return np.dot(Axes3D.get_proj(ax), scale)\n", "\n", "ax.get_proj=short_proj\n", "\n", "fig.subplots_adjust(left=0, right=1, bottom=0, top=1)\n", "# ------------------------------------------------------------------\n", "\n", "# show the plot\n", "plt.show()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def update_mesh_lines_3D(num, data, columns, dates, cmap, lines, mesh_lines, ax):\n", " '''\n", " Function that updates the lines of a plot in 2D\n", " '''\n", " \n", " # get the slice\n", "# current_slice = data[num:261+num, :]\n", " current_slice = data[num:int(261/2)+num, :]\n", " \n", " # for each index...\n", " for i in range(current_slice.shape[1]):\n", "\n", " # get the coordinates\n", " x = np.arange(current_slice.shape[0])\n", " y = np.tile(i, current_slice.shape[0])\n", " z = np.array(current_slice[:, i])\n", "\n", " # crete points and segments to color\n", " points = np.array([x, y, z]).T.reshape(-1, 1, 3)\n", " segments = np.concatenate([points[:-1], points[1:]], axis=1)\n", "\n", " # Create a continuous norm to map from data points to colors\n", " norm = plt.Normalize(-0.19, 0.19) \n", " lines[i].set_segments(segments)\n", " lines[i].set_array(z)\n", " lines[i].set_color(cmap(z[-1] * 2.5 + 0.5))\n", "\n", " # counter to check the current mesh line\n", " counter = 0\n", " \n", " # for each day...\n", " for j in range(current_slice.shape[0]):\n", "\n", " if j % 1 == 0:\n", " \n", " # get the coordinates\n", " x = np.tile(j, current_slice.shape[1])\n", " y = np.arange(current_slice.shape[1])\n", " z = np.array(current_slice[j, :])\n", "\n", " # crete points and segments to color\n", " points = np.array([x, y, z]).T.reshape(-1, 1, 3)\n", " segments = np.concatenate([points[:-1], points[1:]], axis=1)\n", "\n", " # Set the values used for colormapping\n", " norm = plt.Normalize(-0.22, 0.22) \n", " mesh_lines[counter].set_segments(segments)\n", " mesh_lines[counter].set_array(z)\n", " counter += 1\n", " \n", " # update the ticks and labels\n", " ax.set_xticklabels([dates[int(val)+num].strftime('%m/%y') for val in ax.get_xticks()[:-1]] + [''], rotation=0, fontdict={'verticalalignment': 'top', 'horizontalalignment': 'center'})\n", " ax.legend(loc='center right', bbox_to_anchor=(1.1, 0.46), fancybox=True, facecolor=(.95,.95,.95,1), framealpha=1, shadow=False, frameon=True, ncol=1, columnspacing=0, prop={'family': 'DejaVu Sans Mono'})\n", " \n", " # return the lines\n", " return lines\n", "\n", "def init_mesh_lines_3D():\n", " for line in lines:\n", " line.set_array([])\n", " return lines" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# create the animation\n", "line_ani = animation.FuncAnimation(fig=fig, \n", " func=update_mesh_lines_3D, \n", " # frames=30,\n", " frames=indexes_rolling.shape[0]-int(261/2),\n", " init_func=init_mesh_lines_3D, \n", " fargs=(indexes_rolling.values, indexes_rolling.columns, indexes_rolling.index, cmap, lines, mesh_lines, ax),\n", " interval=100, \n", " blit=True)\n", "\n", "# show the animation\n", "line_ani" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# # callback to show the evolution\n", "# progress_callback = lambda i, n: print('Saving frame {:.0%}'.format(i/n)) if int((i/n) * 100) % 10 == 0 else None\n", "\n", "# # save the animation\n", "# line_ani.save('./3D_mesh_animation.gif', writer='imagemagick', fps=14, dpi=80, codec='h264', bitrate=2048, progress_callback=progress_callback)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "phoenix", "language": "python", "name": "phoenix" }, "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.7" } }, "nbformat": 4, "nbformat_minor": 4 }