Python s这是为tradewave.net上的比特币交易而设计的

Python s这是为tradewave.net上的比特币交易而设计的,python,algorithm,numpy,pandas,Python,Algorithm,Numpy,Pandas,这里有一个Numba加速解决方案: 将熊猫作为pd导入 将numpy作为np导入 进口麻木 从时间导入时间 n=10000 返回值=局部放电系列(np.随机.正常(1.001,0.01,n),局部放电日期范围(“2020-01-01”,周期=n,频率=“1min”)) @纳巴恩吉特 def最大提取量(累计返回量): 最大水位降=0.0 当前_max_ret=cum_返回[0] 对于合并报表中的ret: 如果ret>当前\u最大\u ret: 当前_max_ret=ret 最大提取=最大(最大提取

这里有一个Numba加速解决方案:

将熊猫作为pd导入
将numpy作为np导入
进口麻木
从时间导入时间
n=10000
返回值=局部放电系列(np.随机.正常(1.001,0.01,n),局部放电日期范围(“2020-01-01”,周期=n,频率=“1min”))
@纳巴恩吉特
def最大提取量(累计返回量):
最大水位降=0.0
当前_max_ret=cum_返回[0]
对于合并报表中的ret:
如果ret>当前\u最大\u ret:
当前_max_ret=ret
最大提取=最大(最大提取,1-提取/当前提取)
返回最大缩尺
t=时间()
滚动(最大提取,原始=真)
打印(“快速:”,时间()-t);
def最大下降速度(x):
返回值(1-x/x.cummax()).max()
t=时间()
滚动1h\u max\u dd\u slow=returns.cumprod().rolling(“1h”).apply(max\u drawdown\u slow,raw=False)
打印(“慢:”,时间()-t);
断言滚动最大dd.equals(滚动最大dd.slow)
输出:

Fast: 0.05633878707885742
Slow: 4.540301084518433

=>80x加速比

这种比较在上下文中有点不公平,因为需要计算
padded\u ser
window\u length
您没有计时。(不过,你的方法仍然以一个数量级获胜。)没错,我只对计算的主要部分进行了计时。我在代码中加入了填充,以获得与系列开头的pandas
rolling\u apply
函数相同的输出。如果结果仅用于“完整”窗口,则填充步骤可能会被删除,而
滚动\u max\u dd
的结果将按窗口长度-1移动。是否有任何理由使用您选择的特定值填充?我发现这个选择有点令人困惑,尽管我不认为它会带来问题。(我可能会用序列的第一个值填充。)此外,我倾向于接受这个答案,但在我接受之前,您介意发布完整解决方案的时间安排吗?也就是说,你能公布一个单一功能的时间安排吗?该功能是我方法的替代品,因此比较结果是一致的?您是否也可以用
n=10000
window=500
发布计时?另外:您的方法是在
numpy
中完成的,而不是
pandas
。那这和NA的有什么关系?我使用
pandas
编写的简单版本以我认为适合当前问题的方式处理NA。我更新了代码,在
rolling\u max\u dd
函数中包含了填充,并更改了定时比较,以使用更大的
n
window\u length
。它不像以前那么令人印象深刻了(填充值现在只是序列的第一个值(不确定我之前是怎么想的)。可以通过使用
fillna
方法预处理序列来处理NA。很抱歉,这使用了tradewave的内置“highcharts”模块
np.random.seed(0)
n = 100
s = pd.Series(np.random.randn(n).cumsum())
s.plot()
plt.show()
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()
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)
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()
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
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
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
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;
        }
    }
}
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;

    }

}
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));
}
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
# 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
Fast: 0.05633878707885742
Slow: 4.540301084518433