{ "cells": [ { "cell_type": "markdown", "source": [ "# Quickstart or _\"How to get 100% return per year\"_" ], "metadata": {} }, { "cell_type": "markdown", "source": [ "First, do some initialization and set debugging level to `debug` to see progress of computation." ], "metadata": {} }, { "cell_type": "code", "execution_count": 1, "source": [ "%matplotlib inline\n", "%load_ext autoreload\n", "%autoreload 2\n", "%config InlineBackend.figure_format = 'svg'\n", "\n", "import numpy as np\n", "import pandas as pd\n", "import seaborn as sns\n", "import datetime as dt\n", "import matplotlib.pyplot as plt\n", "\n", "import universal as up\n", "from universal import tools, algos\n", "from universal.algos import *\n", "\n", "sns.set_context(\"notebook\")\n", "plt.rcParams[\"figure.figsize\"] = (16, 8)" ], "outputs": [], "metadata": {} }, { "cell_type": "code", "execution_count": 2, "source": [ "# ignore logged warnings\n", "import logging\n", "logging.getLogger().setLevel(logging.ERROR)" ], "outputs": [], "metadata": {} }, { "cell_type": "markdown", "source": [ "Let's try to replicate the results of B.Li and S.Hoi from their article [On-Line Portfolio Selection with Moving Average Reversion](http://arxiv.org/abs/1206.4626). They claim superior performance on several datasets using their OLMAR algorithm. These datasets are available in `data/` directory in `.pkl` format. Those are all relative prices (start with 1.) and artificial tickers. We can start with NYSE stocks from period 1/1/1985 - 30/6/2010." ], "metadata": {} }, { "cell_type": "code", "execution_count": 3, "source": [ "# load data using tools module\n", "data = tools.dataset('nyse_o')\n", "\n", "# plot first three of them as example\n", "data.iloc[:,:3].plot()" ], "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "" ] }, "metadata": {}, "execution_count": 3 }, { "output_type": "display_data", "data": { "text/plain": [ "
" ], "image/svg+xml": "\n\n\n\n \n \n \n \n 2020-08-31T21:30:06.204946\n image/svg+xml\n \n \n Matplotlib v3.3.1, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n" }, "metadata": { "needs_background": "light" } } ], "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } } }, { "cell_type": "markdown", "source": [ "Now we need an implementation of the OLMAR algorithm. Fortunately, it is already implemented in module `algos`, so all we have to do is load it and set its parameters. Authors recommend lookback window $w = 5$ and threshold $\\epsilon = 10$ (these are default parameters anyway). Just call `run` method on our data to get results for analysis." ], "metadata": {} }, { "cell_type": "code", "execution_count": 4, "source": [ "# set algo parameters\n", "algo = algos.OLMAR(window=5, eps=10)\n", "\n", "# run\n", "result = algo.run(data)" ], "outputs": [], "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } } }, { "cell_type": "markdown", "source": [ "Ok, let's see some results. First print some basic summary metrics and plot portfolio equity with UCRP (uniform constant rebalanced portfolio)." ], "metadata": {} }, { "cell_type": "code", "execution_count": 5, "source": [ "print(result.summary())\n", "result.plot(weights=False, assets=False, ucrp=True, logy=True);" ], "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Summary:\n", " Profit factor: 1.89\n", " Sharpe ratio: 3.34 ± 0.54\n", " Ulcer index: 23.19\n", " Information ratio (wrt UCRP): 3.27\n", " Appraisal ratio (wrt UCRP): 3.05 ± 0.21\n", " UCRP sharpe: 1.16 ± 0.27\n", " Beta / Alpha: 1.55 / 165.350%\n", " Annualized return: 466.14%\n", " Annualized volatility: 56.69%\n", " Longest drawdown: 185 days\n", " Max drawdown: 46.29%\n", " Winning days: 58.3%\n", " Annual turnover: 349.1\n", " \n" ] }, { "output_type": "display_data", "data": { "text/plain": [ "
" ], "image/svg+xml": "\n\n\n\n \n \n \n \n 2020-08-31T21:30:16.846929\n image/svg+xml\n \n \n Matplotlib v3.3.1, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n" }, "metadata": { "needs_background": "light" } } ], "metadata": {} }, { "cell_type": "markdown", "source": [ "That seems really impressive, in fact it looks too good to be true. Let's see how individual stocks contribute to portfolio equity and disable legend to keep the graph clean." ], "metadata": {} }, { "cell_type": "code", "execution_count": 6, "source": [ "result.plot_decomposition(legend=False, logy=True)" ], "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "" ] }, "metadata": {}, "execution_count": 6 }, { "output_type": "display_data", "data": { "text/plain": [ "
" ], "image/svg+xml": "\n\n\n\n \n \n \n \n 2020-08-31T21:30:17.276020\n image/svg+xml\n \n \n Matplotlib v3.3.1, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n" }, "metadata": { "needs_background": "light" } } ], "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } } }, { "cell_type": "markdown", "source": [ "As you can see, almost all wealth comes from single stock (don't forget it has logarithm scale!). So if we used just 5 of all these stocks, we would get almost the same equity as if we used all of them. To stress test the strategy, we can remove that stock and rerun the algorithm." ], "metadata": {} }, { "cell_type": "code", "execution_count": 7, "source": [ "# find name of the most profitable asset\n", "most_profitable = result.equity_decomposed.iloc[-1].idxmax()\n", "\n", "# rerun an algorithm on data without it\n", "result_without = algo.run(data.drop([most_profitable], 1))\n", "\n", "# and print results\n", "print(result_without.summary())\n", "result_without.plot(weights=False, assets=False, ucrp=True, logy=True);" ], "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Summary:\n", " Profit factor: 1.55\n", " Sharpe ratio: 2.48 ± 0.43\n", " Ulcer index: 12.67\n", " Information ratio (wrt UCRP): 2.36\n", " Appraisal ratio (wrt UCRP): 2.14 ± 0.21\n", " UCRP sharpe: 1.12 ± 0.27\n", " Beta / Alpha: 1.48 / 96.840%\n", " Annualized return: 192.81%\n", " Annualized volatility: 47.90%\n", " Longest drawdown: 202 days\n", " Max drawdown: 45.91%\n", " Winning days: 56.5%\n", " Annual turnover: 346.1\n", " \n" ] }, { "output_type": "display_data", "data": { "text/plain": [ "
" ], "image/svg+xml": "\n\n\n\n \n \n \n \n 2020-08-31T21:30:27.892143\n image/svg+xml\n \n \n Matplotlib v3.3.1, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n" }, "metadata": { "needs_background": "light" } } ], "metadata": {} }, { "cell_type": "markdown", "source": [ "We lost about 7 orders of wealth, but the results are more realistic now. Let's move on and try adding fees of 0.1% per transaction (we pay \\\\$1 for every \\\\$1000 of stocks bought or sold)." ], "metadata": {} }, { "cell_type": "code", "execution_count": 8, "source": [ "result_without.fee = 0.001\n", "print(result_without.summary())\n", "result_without.plot(weights=False, assets=False, ucrp=True, logy=True)" ], "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Summary:\n", " Profit factor: 1.35\n", " Sharpe ratio: 1.79 ± 0.34\n", " Ulcer index: 6.76\n", " Information ratio (wrt UCRP): 1.61\n", " Appraisal ratio (wrt UCRP): 1.41 ± 0.21\n", " UCRP sharpe: 1.12 ± 0.27\n", " Beta / Alpha: 1.47 / 62.608%\n", " Annualized return: 108.72%\n", " Annualized volatility: 47.17%\n", " Longest drawdown: 382 days\n", " Max drawdown: 50.09%\n", " Winning days: 50.2%\n", " Annual turnover: 346.1\n", " \n" ] }, { "output_type": "execute_result", "data": { "text/plain": [ "[]" ] }, "metadata": {}, "execution_count": 8 }, { "output_type": "display_data", "data": { "text/plain": [ "
" ], "image/svg+xml": "\n\n\n\n \n \n \n \n 2020-08-31T21:30:28.827428\n image/svg+xml\n \n \n Matplotlib v3.3.1, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n