Skip to content

Instantly share code, notes, and snippets.

@8one6
Last active January 3, 2016 23:09
Show Gist options
  • Save 8one6/8532955 to your computer and use it in GitHub Desktop.
Save 8one6/8532955 to your computer and use it in GitHub Desktop.
Drawdown functions using Cython
{
"metadata": {
"name": "Drawdown cython"
},
"nbformat": 3,
"nbformat_minor": 0,
"worksheets": [
{
"cells": [
{
"cell_type": "code",
"collapsed": false,
"input": "import numpy as np\nfrom numpy.lib.stride_tricks import as_strided\nimport matplotlib.pyplot as plt\n\n%matplotlib inline\n%load_ext cythonmagic",
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 1
},
{
"cell_type": "code",
"collapsed": false,
"input": "%%cython\nimport numpy as np\ncimport numpy as np\nfrom libcpp cimport bool\ncimport cython\n\nDTYPE = np.float64\nctypedef np.float64_t DTYPE_t\n\[email protected](False)\[email protected](False)\[email protected](False)\ncpdef tuple cy_dd_custom(np.ndarray[DTYPE_t, ndim=1] ser):\n cdef double running_global_peak = ser[0]\n cdef double min_since_global_peak = ser[0]\n cdef double running_max_dd = 0\n \n cdef long running_global_peak_id = 0\n cdef long running_max_dd_peak_id = 0\n cdef long running_max_dd_trough_id = 0\n \n cdef long i\n cdef double val\n for i in xrange(ser.shape[0]):\n val = ser[i]\n if val >= running_global_peak:\n running_global_peak = val\n running_global_peak_id = i\n min_since_global_peak = val\n if val < min_since_global_peak:\n min_since_global_peak = val\n if val - running_global_peak <= running_max_dd:\n running_max_dd = val - running_global_peak\n running_max_dd_peak_id = running_global_peak_id\n running_max_dd_trough_id = i\n return (running_max_dd, running_max_dd_peak_id, running_max_dd_trough_id, running_global_peak_id)\n\[email protected](False)\[email protected](False)\[email protected](False)\ncpdef np.ndarray[DTYPE_t, ndim=1] cy_rolling_dd_custom(np.ndarray[DTYPE_t, ndim=1] ser, long window):\n cdef np.ndarray[DTYPE_t, ndim=2] result\n result = np.zeros((ser.shape[0], 4))\n \n cdef double running_global_peak = ser[0]\n cdef double min_since_global_peak = ser[0]\n cdef double running_max_dd = 0\n cdef long running_global_peak_id = 0\n cdef long running_max_dd_peak_id = 0\n cdef long running_max_dd_trough_id = 0\n cdef long i\n cdef double val\n cdef int prob_1\n cdef int prob_2\n cdef tuple intermed\n \n for i in xrange(ser.shape[0]):\n val = ser[i]\n if i < window:\n if val >= running_global_peak:\n running_global_peak = val\n running_global_peak_id = i\n min_since_global_peak = val\n if val < min_since_global_peak:\n min_since_global_peak = val\n if val - running_global_peak <= running_max_dd:\n running_max_dd = val - running_global_peak\n running_max_dd_peak_id = running_global_peak_id\n running_max_dd_trough_id = i\n \n result[i, 0] = <double>running_max_dd\n result[i, 1] = <double>running_max_dd_peak_id\n result[i, 2] = <double>running_max_dd_trough_id\n result[i, 3] = <double>running_global_peak_id\n \n else:\n prob_1 = 1 if result[i-1, 3] <= float(i - window) else 0\n prob_2 = 1 if result[i-1, 1] <= float(i - window) else 0\n if prob_1 or prob_2:\n intermed = cy_dd_custom(ser[i-window+1:i+1])\n result[i, 0] = <double>intermed[0]\n result[i, 1] = <double>(intermed[1] + i - window + 1)\n result[i, 2] = <double>(intermed[2] + i - window + 1)\n result[i, 3] = <double>(intermed[3] + i - window + 1)\n else:\n result[i, 3] = i if ser[i] >= ser[result[i-1, 3]] else result[i-1, 3]\n if val - ser[result[i-1, 3]] <= result[i-1, 0]:\n result[i, 0] = <double>(val - ser[result[i-1, 3]])\n result[i, 1] = <double>result[i-1, 3]\n result[i, 2] = <double>i\n else:\n result[i, 0] = <double>result[i-1, 0]\n result[i, 1] = <double>result[i-1, 1]\n result[i, 2] = <double>result[i-1, 2]\n return result[:, 0]",
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 2
},
{
"cell_type": "code",
"collapsed": false,
"input": "def py_dd_custom(ser):\n running_global_peak = ser[0]\n min_since_global_peak = ser[0]\n running_max_dd = 0\n running_global_peak_id = np.nan\n running_max_dd_peak_id = np.nan\n running_max_dd_trough_id = np.nan\n\n for i, val in enumerate(ser):\n if val >= running_global_peak:\n running_global_peak = val\n running_global_peak_id = i\n min_since_global_peak = val\n if val < min_since_global_peak:\n min_since_global_peak = val\n if val - running_global_peak <= running_max_dd:\n running_max_dd = val - running_global_peak\n running_max_dd_peak_id = running_global_peak_id\n running_max_dd_trough_id = i\n return (running_max_dd, running_max_dd_peak_id, running_max_dd_trough_id, running_global_peak_id)",
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 3
},
{
"cell_type": "code",
"collapsed": false,
"input": "def windowed_view(x, window_size):\n y = as_strided(x, shape=(x.size - window_size + 1, window_size),\n strides=(x.strides[0], x.strides[0]))\n return y\n\ndef py_so_rolling_max_dd(x, window_size):\n pad = np.empty(window_size - 1)\n pad.fill(x[0])\n x = np.concatenate((pad, x))\n y = windowed_view(x, window_size)\n running_max_y = np.maximum.accumulate(y, axis=1)\n dd = y - running_max_y\n return dd.min(axis=1)",
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 4
},
{
"cell_type": "code",
"collapsed": false,
"input": "test = np.random.randn(100000).cumsum()\nmaxdd = cy_dd_custom(test)\n\nfig, ax = plt.subplots(1, 1)\nax.plot(test)\nax.plot(maxdd[1], test[maxdd[1]], 'go')\nax.plot(maxdd[2], test[maxdd[2]], 'ro')",
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 5,
"text": "[<matplotlib.lines.Line2D at 0x408e150>]"
},
{
"metadata": {},
"output_type": "display_data",
"png": "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\nRU5ErkJggg==\n",
"text": "<matplotlib.figure.Figure at 0x4076190>"
}
],
"prompt_number": 5
},
{
"cell_type": "code",
"collapsed": false,
"input": "for serlen in [1e3, 1e4]:\n ser = np.random.randn(serlen)\n print 'Series length = %d' % serlen\n print ' Python float drawdown'\n %timeit py_dd_custom(ser)\n print ' Cython float drawdown'\n %timeit cy_dd_custom(ser)\n for winfrac in [.01, .10, .25]:\n print ' Window fraction = %.2f' % winfrac\n print ' Numpy rolling drawdown'\n %timeit py_so_rolling_max_dd(ser, int(serlen * winfrac))\n print ' Cython rolling drawdown'\n %timeit cy_rolling_dd_custom(ser, int(serlen * winfrac)) ",
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": "Series length = 1000\n Python float drawdown\n1000 loops, best of 3: 291 \u00b5s per loop"
},
{
"output_type": "stream",
"stream": "stdout",
"text": "\n Cython float drawdown\n100000 loops, best of 3: 1.76 \u00b5s per loop"
},
{
"output_type": "stream",
"stream": "stdout",
"text": "\n Window fraction = 0.01\n Numpy rolling drawdown\n1000 loops, best of 3: 221 \u00b5s per loop"
},
{
"output_type": "stream",
"stream": "stdout",
"text": "\n Cython rolling drawdown\n100 loops, best of 3: 3.81 ms per loop"
},
{
"output_type": "stream",
"stream": "stdout",
"text": "\n Window fraction = 0.10\n Numpy rolling drawdown\n1000 loops, best of 3: 976 \u00b5s per loop"
},
{
"output_type": "stream",
"stream": "stdout",
"text": "\n Cython rolling drawdown\n100 loops, best of 3: 2.76 ms per loop"
},
{
"output_type": "stream",
"stream": "stdout",
"text": "\n Window fraction = 0.25\n Numpy rolling drawdown\n100 loops, best of 3: 2.59 ms per loop"
},
{
"output_type": "stream",
"stream": "stdout",
"text": "\n Cython rolling drawdown\n100 loops, best of 3: 3.15 ms per loop"
},
{
"output_type": "stream",
"stream": "stdout",
"text": "\nSeries length = 10000\n Python float drawdown\n100 loops, best of 3: 1.89 ms per loop"
},
{
"output_type": "stream",
"stream": "stdout",
"text": "\n Cython float drawdown\n100000 loops, best of 3: 10.8 \u00b5s per loop"
},
{
"output_type": "stream",
"stream": "stdout",
"text": "\n Window fraction = 0.01\n Numpy rolling drawdown\n100 loops, best of 3: 16.2 ms per loop"
},
{
"output_type": "stream",
"stream": "stdout",
"text": "\n Cython rolling drawdown\n10 loops, best of 3: 41.9 ms per loop"
},
{
"output_type": "stream",
"stream": "stdout",
"text": "\n Window fraction = 0.10\n Numpy rolling drawdown\n10 loops, best of 3: 146 ms per loop"
},
{
"output_type": "stream",
"stream": "stdout",
"text": "\n Cython rolling drawdown\n10 loops, best of 3: 31.8 ms per loop"
},
{
"output_type": "stream",
"stream": "stdout",
"text": "\n Window fraction = 0.25\n Numpy rolling drawdown\n1 loops, best of 3: 340 ms per loop"
},
{
"output_type": "stream",
"stream": "stdout",
"text": "\n Cython rolling drawdown\n10 loops, best of 3: 27.1 ms per loop"
},
{
"output_type": "stream",
"stream": "stdout",
"text": "\n"
}
],
"prompt_number": 14
}
],
"metadata": {}
}
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment