简单的移动平均值" live"流 - 快速实施

时间:2014-04-28 10:21:36

标签: c++ algorithmic-trading

在我的交易申请中,我有股票价格的“现场价格”。我需要保持SMA。我们假设我想要20支蜡烛的SMA,每支蜡烛的持续时间为10秒。这意味着

每隔10秒我就有“检查点”,其中:

  1. 我“关闭”当前蜡烛并存储过去10秒的平均价格。平均值是(最大 - 最小)/ 2
  2. 我“开始”新蜡烛并存储最后价格。
  3. 我清理“过时的”蜡烛。
  4. 每个嘀嗒声:

    1. 我更新当前“形成”蜡烛的“最后”价格并重新计算SMA。
    2. 所以在任何时候我需要“重新计算”SMA。在大多数情况下,只更改最后一根蜡烛的价格(因为我们使用最后价格)。每10秒一次,我需要多做一些额外的工作 - 我需要“忘记”过时蜡烛的平均值,并“存储”“刚创建”蜡烛的平均值。

      您能否建议如何以最低延迟实现此目标?低延迟是首要要求。

3 个答案:

答案 0 :(得分:3)

我不确定这是否是您正在寻找的方法,但这里是非常快的SMA的伪代码。

简单移动平均线:

我假设您的数据以某种流的形式出现并存储在连续的内存位置(至少具有连续可映射的地址)

x[1] to x[2000] contain the first 2000 data points

// they don't have to be a real array, could just be a function which manages
// a 'circular' array and maps the 'locations' to real locations in memory.
// Either case, it's small enough to be fully in the cache hopefully
//
// Subsequent prices will be accessible in 'locations x[2001], etc.
// Here we are looking to calculate the MA over the latest 2000 ticks

MA2000(1,2000) = (x[1] + x[2] + ... + x[2000]) / 2000 // Usual average
                                                      // Done only once

MA2000(2,2001) = MA2000(1,2000) * 2000 + x[2001] - x[1]
MA2000(2,2001) /= 2000

通过两次加法和一次乘法(使用1/2000),您可以为新的刻度生成后续移动平均值。

指数移动平均线: 如上所述,这是一个不错的选择:

// For an N-day EMA
Alpha = 2 / (N+1)      // one time calculation

EMA(new) = Alpha * NewTickPrice + (1-Alpha) * EMA(old)

这不是N日移动平均线。它只是一个加权移动平均线,最近N天的权重约为87%,因此几乎N天更像是它。

关于编译器优化的注意事项:

请注意,如果可用,启用SSE或AVX选项可以实现这些算法的大量加速,因为可以在单个CPU周期中生成多个计算。

答案 1 :(得分:0)

因此,您需要一个几乎固定大小的队列,您可以在其中有效地添加新项目并删除最旧的项目(将其从运行总计中删除)。为什么不std::queue

这可以放在各种容器之上,但是如果你真的只有20个元素,我怀疑vector会表现得很好。 (删除项目需要将所有其他项目向下移动一个 - 但移动连续的内存块很快。)您可能希望将性能与双端队列或列表进行比较。

(答案可能取决于你为每个人存储的内容"蜡烛" - 只是一个浮点数/双/ int,或者更复杂的结构?)

答案 2 :(得分:0)

我的实施。 .H:

#pragma once

#include <deque>

class MovingAverage
{
public:
    MovingAverage(int length);
    ~MovingAverage(void);
    void Add(double val);
    void Modify(double value);
    double Value;
    std::deque<double> _candlesExceptNewest;
private:
    MovingAverage(MovingAverage& rhs):
        _length(rhs._length)
    {
        printf("MovingAverage copy-constructor mustn't be executed, exiting.");
        exit(0);
    }

    const int _length;

    int addCounter;
    static const int RECALCULATE_VALUE_MASK;

    double _sumExceptNewest;

    double NewestCandleMedian() {
        return (_newestCandleMin + _newestCandleMax) / 2;
    }
    void RecalculateValue();
    double _newestCandleMin;
    double _newestCandleMax;
};

的.cpp:

#include "MovingAverage.h"
#include "CommonsNative.h"

const int MovingAverage::RECALCULATE_VALUE_MASK = 1024 - 1;

MovingAverage::MovingAverage(int length):
    Value(quiet_NaN),
    _length(length),
    addCounter(0)
{
    if (length < 20) {
        std::cout << "Warning, MA is very short, less than 20! length = " 
            << length << std::endl;
    }
}

MovingAverage::~MovingAverage(void)
{
}

void MovingAverage::Add(double val)
{
    ++addCounter;
    if (addCounter & RECALCULATE_VALUE_MASK) {
        _sumExceptNewest = 0;
        for (double val : _candlesExceptNewest)
        {
            _sumExceptNewest += val;
        }
    }

    auto curQueueSize = _candlesExceptNewest.size();
    if (curQueueSize == 0) {
        _newestCandleMax = _newestCandleMin = val;
    }
    _sumExceptNewest += NewestCandleMedian();
    _candlesExceptNewest.push_back(NewestCandleMedian());
    if (curQueueSize == _length) {
        _sumExceptNewest -= _candlesExceptNewest.front();
        _candlesExceptNewest.pop_front();
    }
    _newestCandleMax = _newestCandleMin = val;
    RecalculateValue();
}

void MovingAverage::RecalculateValue()
{
    Value = (_sumExceptNewest + NewestCandleMedian())/(_candlesExceptNewest.size() + 1);
}

void MovingAverage::Modify(double val)
{
    if (_candlesExceptNewest.size() == 0) {
        Add(val);
    } else {
        if (val > _newestCandleMax) {
            _newestCandleMax = val;
        } 
        if (val < _newestCandleMin) {
            _newestCandleMin = val;
        }
    }
}