inplace index example python algorithm numpy pandas

python - index - Compute*rolling*máximo drawdown de pandas Series



pandas series example (4)

Es bastante fácil escribir una función que calcula la reducción máxima de una serie de tiempo. Se necesita un poco de pensamiento para escribirlo en tiempo O(n) lugar de tiempo O(n^2) . Pero no es tan malo. Esto funcionará:

import numpy as np import pandas as pd import matplotlib.pyplot as plt def max_dd(ser): max2here = pd.expanding_max(ser) dd2here = ser - max2here return dd2here.min()

Vamos a configurar una serie breve para jugar para probarlo:

np.random.seed(0) n = 100 s = pd.Series(np.random.randn(n).cumsum()) s.plot() plt.show()

Como se esperaba, max_dd(s) termina mostrando algo alrededor de -17.6. Bien, grandioso, grandioso. Ahora digamos que estoy interesado en calcular la reducción progresiva de esta serie. Es decir, para cada paso, quiero calcular la reducción máxima de la subserie anterior de una longitud específica. Esto es fácil de hacer usando pd.rolling_apply . Funciona así:

rolling_dd = pd.rolling_apply(s, 10, max_dd, min_periods=0) df = pd.concat([s, rolling_dd], axis=1) df.columns = [''s'', ''rol_dd_10''] df.plot()

Esto funciona perfectamente. Pero se siente muy lento. ¿Hay un algoritmo particularmente hábil en pandas u otro conjunto de herramientas para hacer esto rápido? Intenté escribir algo a medida: realiza un seguimiento de todo tipo de datos intermedios (ubicaciones de los máximos observados, ubicaciones de las reducciones encontradas previamente) para reducir muchos cálculos redundantes. Se ahorra tiempo, pero no mucho, y no tanto como debería ser posible.

Creo que se debe a todos los bucles de Python / Numpy / Pandas. Pero actualmente no tengo suficiente fluidez en Cython para saber realmente cómo comenzar a atacar esto desde ese ángulo. Esperaba que alguien hubiera intentado esto antes. O, quizás, que alguien pueda querer echar un vistazo a mi código "hecho a mano" y estar dispuesto a ayudarme a convertirlo en Cython.

Edición: para cualquier persona que quiera revisar todas las funciones mencionadas aquí (¡y algunas más!), Eche un vistazo al cuaderno de iPython en: http://nbviewer.ipython.org/gist/8one6/8506455

Muestra cómo se relacionan algunos de los enfoques de este problema, comprueba que dan los mismos resultados y muestra sus tiempos de ejecución en datos de varios tamaños.

Si alguien está interesado, el algoritmo "a medida" que rolling_dd_custom en mi publicación es rolling_dd_custom . Creo que podría ser una solución muy rápida si se implementa en Cython.


Aquí hay una versión numpy de la función de reducción máxima de rodadura. windowed_view es una envoltura de una función de una línea que usa numpy.lib.stride_tricks.as_strided para hacer una vista de la ventana 2d eficiente de la memoria de la matriz 1d (código completo a continuación). Una vez que tenemos esta vista de ventana, el cálculo es básicamente el mismo que su max_dd , pero se escribe para una matriz numpy y se aplica a lo largo del segundo eje (es decir, axis=1 ).

def rolling_max_dd(x, window_size, min_periods=1): """Compute the rolling maximum drawdown of `x`. `x` must be a 1d numpy array. `min_periods` should satisfy `1 <= min_periods <= window_size`. Returns an 1d array with length `len(x) - min_periods + 1`. """ if min_periods < window_size: pad = np.empty(window_size - min_periods) pad.fill(x[0]) x = np.concatenate((pad, x)) y = windowed_view(x, window_size) running_max_y = np.maximum.accumulate(y, axis=1) dd = y - running_max_y return dd.min(axis=1)

Aquí hay un script completo que demuestra la función:

import numpy as np from numpy.lib.stride_tricks import as_strided import pandas as pd import matplotlib.pyplot as plt def windowed_view(x, window_size): """Creat a 2d windowed view of a 1d array. `x` must be a 1d numpy array. `numpy.lib.stride_tricks.as_strided` is used to create the view. The data is not copied. Example: >>> x = np.array([1, 2, 3, 4, 5, 6]) >>> windowed_view(x, 3) array([[1, 2, 3], [2, 3, 4], [3, 4, 5], [4, 5, 6]]) """ y = as_strided(x, shape=(x.size - window_size + 1, window_size), strides=(x.strides[0], x.strides[0])) return y def rolling_max_dd(x, window_size, min_periods=1): """Compute the rolling maximum drawdown of `x`. `x` must be a 1d numpy array. `min_periods` should satisfy `1 <= min_periods <= window_size`. Returns an 1d array with length `len(x) - min_periods + 1`. """ if min_periods < window_size: pad = np.empty(window_size - min_periods) pad.fill(x[0]) x = np.concatenate((pad, x)) y = windowed_view(x, window_size) running_max_y = np.maximum.accumulate(y, axis=1) dd = y - running_max_y return dd.min(axis=1) def max_dd(ser): max2here = pd.expanding_max(ser) dd2here = ser - max2here return dd2here.min() if __name__ == "__main__": np.random.seed(0) n = 100 s = pd.Series(np.random.randn(n).cumsum()) window_length = 10 rolling_dd = pd.rolling_apply(s, window_length, max_dd, min_periods=0) df = pd.concat([s, rolling_dd], axis=1) df.columns = [''s'', ''rol_dd_%d'' % window_length] df.plot(linewidth=3, alpha=0.4) my_rmdd = rolling_max_dd(s.values, window_length, min_periods=1) plt.plot(my_rmdd, ''g.'') plt.show()

La gráfica muestra las curvas generadas por su código. Los puntos verdes se calculan mediante rolling_max_dd .

Comparación de tiempo, con n = 10000 y window_length = 500 :

In [2]: %timeit rolling_dd = pd.rolling_apply(s, window_length, max_dd, min_periods=0) 1 loops, best of 3: 247 ms per loop In [3]: %timeit my_rmdd = rolling_max_dd(s.values, window_length, min_periods=1) 10 loops, best of 3: 38.2 ms per loop

rolling_max_dd es aproximadamente 6.5 veces más rápido. La aceleración es mejor para longitudes de ventana más pequeñas. Por ejemplo, con window_length = 200 , es casi 13 veces más rápido.

Para manejar las NA, puede preprocesar la Series utilizando el método fillna antes de pasar la matriz a rolling_max_dd .


En aras de la posteridad y de la integridad, esto es lo que encontré en Cython. MemoryViews aceleró materialmente las cosas. Había un poco de trabajo que hacer para asegurarse de que había escrito correctamente todo (lo siento, nuevo para los lenguajes de tipo c). Pero al final creo que funciona muy bien. Para los casos de uso típicos, el aumento de velocidad frente a Python normal fue ~ 100x o ~ 150x. La función a la que llamar es cy_rolling_dd_custom_mv donde el primer argumento ( ser ) debe ser una matriz numpy 1-d y el segundo argumento ( window ) debe ser un entero positivo. La función devuelve una vista de memoria numpy, que funciona lo suficientemente bien en la mayoría de los casos. Puede llamar explícitamente a np.array(result) si necesita obtener una buena matriz de la salida:

import numpy as np cimport numpy as np cimport cython DTYPE = np.float64 ctypedef np.float64_t DTYPE_t @cython.boundscheck(False) @cython.wraparound(False) @cython.nonecheck(False) cpdef tuple cy_dd_custom_mv(double[:] ser): cdef double running_global_peak = ser[0] cdef double min_since_global_peak = ser[0] cdef double running_max_dd = 0 cdef long running_global_peak_id = 0 cdef long running_max_dd_peak_id = 0 cdef long running_max_dd_trough_id = 0 cdef long i cdef double val for i in xrange(ser.shape[0]): val = ser[i] if val >= running_global_peak: running_global_peak = val running_global_peak_id = i min_since_global_peak = val if val < min_since_global_peak: min_since_global_peak = val if val - running_global_peak <= running_max_dd: running_max_dd = val - running_global_peak running_max_dd_peak_id = running_global_peak_id running_max_dd_trough_id = i return (running_max_dd, running_max_dd_peak_id, running_max_dd_trough_id, running_global_peak_id) @cython.boundscheck(False) @cython.wraparound(False) @cython.nonecheck(False) def cy_rolling_dd_custom_mv(double[:] ser, long window): cdef double[:, :] result result = np.zeros((ser.shape[0], 4)) cdef double running_global_peak = ser[0] cdef double min_since_global_peak = ser[0] cdef double running_max_dd = 0 cdef long running_global_peak_id = 0 cdef long running_max_dd_peak_id = 0 cdef long running_max_dd_trough_id = 0 cdef long i cdef double val cdef int prob_1 cdef int prob_2 cdef tuple intermed cdef long newthing for i in xrange(ser.shape[0]): val = ser[i] if i < window: if val >= running_global_peak: running_global_peak = val running_global_peak_id = i min_since_global_peak = val if val < min_since_global_peak: min_since_global_peak = val if val - running_global_peak <= running_max_dd: running_max_dd = val - running_global_peak running_max_dd_peak_id = running_global_peak_id running_max_dd_trough_id = i result[i, 0] = <double>running_max_dd result[i, 1] = <double>running_max_dd_peak_id result[i, 2] = <double>running_max_dd_trough_id result[i, 3] = <double>running_global_peak_id else: prob_1 = 1 if result[i-1, 3] <= float(i - window) else 0 prob_2 = 1 if result[i-1, 1] <= float(i - window) else 0 if prob_1 or prob_2: intermed = cy_dd_custom_mv(ser[i-window+1:i+1]) result[i, 0] = <double>intermed[0] result[i, 1] = <double>(intermed[1] + i - window + 1) result[i, 2] = <double>(intermed[2] + i - window + 1) result[i, 3] = <double>(intermed[3] + i - window + 1) else: newthing = <long>(int(result[i-1, 3])) result[i, 3] = i if ser[i] >= ser[newthing] else result[i-1, 3] if val - ser[newthing] <= result[i-1, 0]: result[i, 0] = <double>(val - ser[newthing]) result[i, 1] = <double>result[i-1, 3] result[i, 2] = <double>i else: result[i, 0] = <double>result[i-1, 0] result[i, 1] = <double>result[i-1, 1] result[i, 2] = <double>result[i-1, 2] cdef double[:] finalresult = result[:, 0] return finalresult


Hola gente. Este es un problema bastante complejo si desea resolverlo de una manera computacionalmente eficiente para una ventana móvil. He seguido adelante y he escrito una solución a esto en C #. Quiero compartir esto ya que el esfuerzo requerido para replicar este trabajo es bastante alto.

Primero, aquí están los resultados:

Aquí tomamos una implementación de reducción simple y volvemos a calcular la ventana completa cada vez.

test1 - simple drawdown test with 30 period rolling window. run 100 times. total seconds 0.8060461 test2 - simple drawdown test with 60 period rolling window. run 100 times. total seconds 1.416081 test3 - simple drawdown test with 180 period rolling window. run 100 times. total seconds 3.6602093 test4 - simple drawdown test with 360 period rolling window. run 100 times. total seconds 6.696383 test5 - simple drawdown test with 500 period rolling window. run 100 times. total seconds 8.9815137

Aquí nos comparamos con los resultados generados por mi eficiente algoritmo de ventana rodante donde solo se agrega la última observación y luego lo hace mágico

test6 - running drawdown test with 30 period rolling window. run 100 times. total seconds 0.2940168 test7 - running drawdown test with 60 period rolling window. run 100 times. total seconds 0.3050175 test8 - running drawdown test with 180 period rolling window. run 100 times. total seconds 0.3780216 test9 - running drawdown test with 360 period rolling window. run 100 times. total seconds 0.4560261 test10 - running drawdown test with 500 period rolling window. run 100 times. total seconds 0.5050288

En la ventana de 500 períodos. Estamos logrando una mejora de 20: 1 en el tiempo de cálculo.

Aquí está el código de la clase de reducción simple utilizada para las comparaciones:

public class SimpleDrawDown { public double Peak { get; set; } public double Trough { get; set; } public double MaxDrawDown { get; set; } public SimpleDrawDown() { Peak = double.NegativeInfinity; Trough = double.PositiveInfinity; MaxDrawDown = 0; } public void Calculate(double newValue) { if (newValue > Peak) { Peak = newValue; Trough = Peak; } else if (newValue < Trough) { Trough = newValue; var tmpDrawDown = Peak - Trough; if (tmpDrawDown > MaxDrawDown) MaxDrawDown = tmpDrawDown; } } }

Y aquí está el código para la implementación eficiente completa. Esperemos que los comentarios del código tengan sentido.

internal class DrawDown { int _n; int _startIndex, _endIndex, _troughIndex; public int Count { get; set; } LinkedList<double> _values; public double Peak { get; set; } public double Trough { get; set; } public bool SkipMoveBackDoubleCalc { get; set; } public int PeakIndex { get { return _startIndex; } } public int TroughIndex { get { return _troughIndex; } } //peak to trough return public double DrawDownAmount { get { return Peak - Trough; } } /// <summary> /// /// </summary> /// <param name="n">max window for drawdown period</param> /// <param name="peak">drawdown peak i.e. start value</param> public DrawDown(int n, double peak) { _n = n - 1; _startIndex = _n; _endIndex = _n; _troughIndex = _n; Count = 1; _values = new LinkedList<double>(); _values.AddLast(peak); Peak = peak; Trough = peak; } /// <summary> /// adds a new observation on the drawdown curve /// </summary> /// <param name="newValue"></param> public void Add(double newValue) { //push the start of this drawdown backwards //_startIndex--; //the end of the drawdown is the current period end _endIndex = _n; //the total periods increases with a new observation Count++; //track what all point values are in the drawdown curve _values.AddLast(newValue); //update if we have a new trough if (newValue < Trough) { Trough = newValue; _troughIndex = _endIndex; } } /// <summary> /// Shift this Drawdown backwards in the observation window /// </summary> /// <param name="trackingNewPeak">whether we are already tracking a new peak or not</param> /// <returns>a new drawdown to track if a new peak becomes active</returns> public DrawDown MoveBack(bool trackingNewPeak, bool recomputeWindow = true) { if (!SkipMoveBackDoubleCalc) { _startIndex--; _endIndex--; _troughIndex--; if (recomputeWindow) return RecomputeDrawdownToWindowSize(trackingNewPeak); } else SkipMoveBackDoubleCalc = false; return null; } private DrawDown RecomputeDrawdownToWindowSize(bool trackingNewPeak) { //the start of this drawdown has fallen out of the start of our observation window, so we have to recalculate the peak of the drawdown if (_startIndex < 0) { Peak = double.NegativeInfinity; _values.RemoveFirst(); Count--; //there is the possibility now that there is a higher peak, within the current drawdown curve, than our first observation //when we find it, remove all data points prior to this point //the new peak must be before the current known trough point int iObservation = 0, iNewPeak = 0, iNewTrough = _troughIndex, iTmpNewPeak = 0, iTempTrough = 0; double newDrawDown = 0, tmpPeak = 0, tmpTrough = double.NegativeInfinity; DrawDown newDrawDownObj = null; foreach (var pointOnDrawDown in _values) { if (iObservation < _troughIndex) { if (pointOnDrawDown > Peak) { iNewPeak = iObservation; Peak = pointOnDrawDown; } } else if (iObservation == _troughIndex) { newDrawDown = Peak - Trough; tmpPeak = Peak; } else { //now continue on through the remaining points, to determine if there is a nested-drawdown, that is now larger than the newDrawDown //e.g. higher peak beyond _troughIndex, with higher trough than that at _troughIndex, but where new peak minus new trough is > newDrawDown if (pointOnDrawDown > tmpPeak) { tmpPeak = pointOnDrawDown; tmpTrough = tmpPeak; iTmpNewPeak = iObservation; //we need a new drawdown object, as we have a new higher peak if (!trackingNewPeak) newDrawDownObj = new DrawDown(_n + 1, tmpPeak); } else { if (!trackingNewPeak && newDrawDownObj != null) { newDrawDownObj.MoveBack(true, false); //recomputeWindow is irrelevant for this as it will never fall before period 0 in this usage scenario newDrawDownObj.Add(pointOnDrawDown); //keep tracking this new drawdown peak } if (pointOnDrawDown < tmpTrough) { tmpTrough = pointOnDrawDown; iTempTrough = iObservation; var tmpDrawDown = tmpPeak - tmpTrough; if (tmpDrawDown > newDrawDown) { newDrawDown = tmpDrawDown; iNewPeak = iTmpNewPeak; iNewTrough = iTempTrough; Peak = tmpPeak; Trough = tmpTrough; } } } } iObservation++; } _startIndex = iNewPeak; //our drawdown now starts from here in our observation window _troughIndex = iNewTrough; for (int i = 0; i < _startIndex; i++) { _values.RemoveFirst(); //get rid of the data points prior to this new drawdown peak Count--; } return newDrawDownObj; } return null; } } public class RunningDrawDown { int _n; List<DrawDown> _drawdownObjs; DrawDown _currentDrawDown; DrawDown _maxDrawDownObj; /// <summary> /// The Peak of the MaxDrawDown /// </summary> public double DrawDownPeak { get { if (_maxDrawDownObj == null) return double.NegativeInfinity; return _maxDrawDownObj.Peak; } } /// <summary> /// The Trough of the Max DrawDown /// </summary> public double DrawDownTrough { get { if (_maxDrawDownObj == null) return double.PositiveInfinity; return _maxDrawDownObj.Trough; } } /// <summary> /// The Size of the DrawDown - Peak to Trough /// </summary> public double DrawDown { get { if (_maxDrawDownObj == null) return 0; return _maxDrawDownObj.DrawDownAmount; } } /// <summary> /// The Index into the Window that the Peak of the DrawDown is seen /// </summary> public int PeakIndex { get { if (_maxDrawDownObj == null) return 0; return _maxDrawDownObj.PeakIndex; } } /// <summary> /// The Index into the Window that the Trough of the DrawDown is seen /// </summary> public int TroughIndex { get { if (_maxDrawDownObj == null) return 0; return _maxDrawDownObj.TroughIndex; } } /// <summary> /// Creates a running window for the calculation of MaxDrawDown within the window /// </summary> /// <param name="n">the number of periods within the window</param> public RunningDrawDown(int n) { _n = n; _currentDrawDown = null; _drawdownObjs = new List<DrawDown>(); } /// <summary> /// The new value to add onto the end of the current window (the first value will drop off) /// </summary> /// <param name="newValue">the new point on the curve</param> public void Calculate(double newValue) { if (double.IsNaN(newValue)) return; if (_currentDrawDown == null) { var drawDown = new DrawDown(_n, newValue); _currentDrawDown = drawDown; _maxDrawDownObj = drawDown; } else { //shift current drawdown back one. and if the first observation falling outside the window means we encounter a new peak after the current trough, we start tracking a new drawdown var drawDownFromNewPeak = _currentDrawDown.MoveBack(false); //this is a special case, where a new lower peak (now the highest) is created due to the drop of of the pre-existing highest peak, and we are not yet tracking a new peak if (drawDownFromNewPeak != null) { _drawdownObjs.Add(_currentDrawDown); //record this drawdown into our running drawdowns list) _currentDrawDown.SkipMoveBackDoubleCalc = true; //MoveBack() is calculated again below in _drawdownObjs collection, so we make sure that is skipped this first time _currentDrawDown = drawDownFromNewPeak; _currentDrawDown.MoveBack(true); } if (newValue > _currentDrawDown.Peak) { //we need a new drawdown object, as we have a new higher peak var drawDown = new DrawDown(_n, newValue); //do we have an existing drawdown object, and does it have more than 1 observation if (_currentDrawDown.Count > 1) { _drawdownObjs.Add(_currentDrawDown); //record this drawdown into our running drawdowns list) _currentDrawDown.SkipMoveBackDoubleCalc = true; //MoveBack() is calculated again below in _drawdownObjs collection, so we make sure that is skipped this first time } _currentDrawDown = drawDown; } else { //add the new observation to the current drawdown _currentDrawDown.Add(newValue); } } //does our new drawdown surpass any of the previous drawdowns? //if so, we can drop the old drawdowns, as for the remainer of the old drawdowns lives in our lookup window, they will be smaller than the new one var newDrawDown = _currentDrawDown.DrawDownAmount; _maxDrawDownObj = _currentDrawDown; var maxDrawDown = newDrawDown; var keepDrawDownsList = new List<DrawDown>(); foreach (var drawDownObj in _drawdownObjs) { drawDownObj.MoveBack(true); if (drawDownObj.DrawDownAmount > newDrawDown) { keepDrawDownsList.Add(drawDownObj); } //also calculate our max drawdown here if (drawDownObj.DrawDownAmount > maxDrawDown) { maxDrawDown = drawDownObj.DrawDownAmount; _maxDrawDownObj = drawDownObj; } } _drawdownObjs = keepDrawDownsList; } }

Ejemplo de uso:

RunningDrawDown rd = new RunningDrawDown(500); foreach (var input in data) { rd.Calculate(input); Console.WriteLine(string.Format("max draw {0:0.00000}, peak {1:0.00000}, trough {2:0.00000}, drawstart {3:0.00000}, drawend {4:0.00000}", rd.DrawDown, rd.DrawDownPeak, rd.DrawDownTrough, rd.PeakIndex, rd.TroughIndex)); }


# BEGIN: TRADEWAVE MOVING AVERAGE CROSSOVER EXAMPLE THRESHOLD = 0.005 INTERVAL = 43200 SHORT = 10 LONG = 90 def initialize(): storage.invested = storage.get(''invested'', False) def tick(): short_term = data(interval=INTERVAL).btc_usd.ma(SHORT) long_term = data(interval=INTERVAL).btc_usd.ma(LONG) diff = 100 * (short_term - long_term) / ((short_term + long_term) / 2) if diff >= THRESHOLD and not storage.invested: buy(pairs.btc_usd) storage.invested = True elif diff <= -THRESHOLD and storage.invested: sell(pairs.btc_usd) storage.invested = False plot(''short_term'', short_term) plot(''long_term'', long_term) # END: TRADEWAVE MOVING AVERAGE CROSSOVER EXAMPLE ############################################################## ############################################################## # BEGIN MAX DRAW DOWN by litepresence # vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv dd() ROLLING = 30 # days def dd(): dd, storage.max_dd = max_dd(0) bnh_dd, storage.max_bnh_dd = bnh_max_dd(0) rolling_dd, storage.max_rolling_dd = max_dd( ROLLING*86400/info.interval) rolling_bnh_dd, storage.max_rolling_bnh_dd = bnh_max_dd( ROLLING*86400/info.interval) plot(''dd'', dd, secondary=True) plot(''bnh_dd'', bnh_dd, secondary=True) plot(''rolling_dd'', rolling_dd, secondary=True) plot(''rolling_bnh_dd'', rolling_bnh_dd, secondary=True) plot(''zero'', 0, secondary=True) if info.tick == 0: plot(''dd_floor'', -200, secondary=True) def max_dd(rolling): port_value = float(portfolio.usd+portfolio.btc*data.btc_usd.price) max_value = ''max_value_'' + str(rolling) values_since_max = ''values_since_max_'' + str(rolling) max_dd = ''max_dd_'' + str(rolling) storage[max_value] = storage.get(max_value, [port_value]) storage[values_since_max] = storage.get(values_since_max, [port_value]) storage[max_dd] = storage.get(max_dd, [0]) storage[max_value].append(port_value) if port_value > max(storage[max_value]): storage[values_since_max] = [port_value] else: storage[values_since_max].append(port_value) storage[max_value] = storage[max_value][-rolling:] storage[values_since_max] = storage[values_since_max][-rolling:] dd = -100*(max(storage[max_value]) - storage[values_since_max][-1] )/max(storage[max_value]) storage[max_dd].append(float(dd)) storage[max_dd] = storage[max_dd][-rolling:] max_dd = min(storage[max_dd]) return (dd, max_dd) def bnh_max_dd(rolling): coin = data.btc_usd.price bnh_max_value = ''bnh_max_value_'' + str(rolling) bnh_values_since_max = ''bnh_values_since_max_'' + str(rolling) bnh_max_dd = ''bnh_max_dd_'' + str(rolling) storage[bnh_max_value] = storage.get(bnh_max_value, [coin]) storage[bnh_values_since_max] = storage.get(bnh_values_since_max, [coin]) storage[bnh_max_dd] = storage.get(bnh_max_dd, [0]) storage[bnh_max_value].append(coin) if coin > max(storage[bnh_max_value]): storage[bnh_values_since_max] = [coin] else: storage[bnh_values_since_max].append(coin) storage[bnh_max_value] = storage[bnh_max_value][-rolling:] storage[bnh_values_since_max] = storage[bnh_values_since_max][-rolling:] bnh_dd = -100*(max(storage[bnh_max_value]) - storage[bnh_values_since_max][-1] )/max(storage[bnh_max_value]) storage[bnh_max_dd].append(float(bnh_dd)) storage[bnh_max_dd] = storage[bnh_max_dd][-rolling:] bnh_max_dd = min(storage[bnh_max_dd]) return (bnh_dd, bnh_max_dd) def stop(): log(''MAX DD......: %.2f pct'' % storage.max_dd) log(''R MAX DD....: %.2f pct'' % storage.max_rolling_dd) log(''MAX BNH DD..: %.2f pct'' % storage.max_bnh_dd) log(''R MAX BNH DD: %.2f pct'' % storage.max_rolling_bnh_dd)

[2015-03-04 00:00:00] MAX DD......: -67.94 pct [2015-03-04 00:00:00] R MAX DD....: -4.93 pct [2015-03-04 00:00:00] MAX BNH DD..: -82.88 pct [2015-03-04 00:00:00] R MAX BNH DD: -26.38 pct

  • Dibujar hacia abajo
  • Max Drawn Down
  • Compra y Mantén Sorteo
  • Comprar y Mantener Max Draw Down
  • Rolling Draw Down
  • Rolling Max Drawn Down
  • Rolling comprar y mantener dibujar
  • Rolling Comprar y Mantener Max Draw Down

Sin pandas, cython, ni dependencias numpy. Todos los cálculos a través de listas simples.

Las definiciones son reutilizables para múltiples tamaños de ventanas rodantes en el mismo script. Deberá editar la entrada de la serie para su plataforma, ya que está diseñada para el comercio de Bitcoin en tradewave.net