跳转至

EMA和EMASTDEV(时间平滑)

#ifndef _EMA_H_
#define _EMA_H_

/**
 * @brief 指数移动平均(Exponential Moving Average, EMA)类
 *
 * EMA 是一种常用的时间序列平滑技术,新值按指数权重衰减历史数据。
 * 更新公式:new_value = alpha * input + (1 - alpha) * old_value
 * 其中 alpha = 2 / (period + 1)
 *
 * 本实现支持编译期构造(constexpr),适用于高频数值计算场景。
 */
class EMA {
 public:
  /**
   * @brief 构造 EMA 对象
   *
   * @param period 平滑周期(必须 > 0),周期越大,响应越慢
   * @param value 初始值,默认为 0
   */
  constexpr EMA(unsigned int period, double value = 0.0)
      : value_(value),
        multiplier_(2.0 / (static_cast<double>(period) + 1.0)),
        period_(period) {}

  /**
   * @brief 从另一个 EMA 复制 multiplier,并指定新的当前值
   *
   * 用于实现 operator+ 等不可变操作
   */
  constexpr EMA(const EMA& other, double value)
      : value_(value), multiplier_(other.multiplier_), period_(other.period_) {}

  /**
   * @brief 不可变加法:返回一个更新后的新 EMA 对象(不修改原对象)
   */
  constexpr EMA operator+(double input) const {
    return EMA(*this, calc_new_value(input));
  }

  /**
   * @brief 原地更新:将当前 EMA 值更新为加入新输入后的结果
   *
   * 注意:此操作有副作用,不能用于 constexpr 上下文(除非对象本身是 constexpr)
   */
  EMA& operator+=(double input) {
    value_ = calc_new_value(input);
    return *this;
  }

  // 显式默认拷贝构造(保持 Rule of Zero)
  EMA(const EMA&) = default;
  EMA& operator=(const EMA&) = default;

  /**
   * @brief 获取当前 EMA 值
   */
  constexpr double operator()() const { return value_; }

  /**
   * @brief 重置 EMA 的当前值(可用于初始化或状态重置)
   */
  void reset(double value) { value_ = value; }

  /**
   * @brief 获取平滑系数 alpha = 2 / (period + 1)
   */
  constexpr double alpha() const { return multiplier_; }

  /**
   * @brief 获取原始周期参数
   */
  constexpr unsigned int period() const { return period_; }

 private:
  double value_ = 0.0;   ///< 当前 EMA 值
  double multiplier_;    ///< 平滑系数 α = 2 / (N + 1)
  unsigned int period_;  ///< 原始周期(便于调试和查询)

  /**
   * @brief 计算加入新输入后的 EMA 值
   *
   * 公式:new_value = α * input + (1 - α) * old_value
   * 等价变形:(input - old_value) * α + old_value(减少一次乘法)
   */
  constexpr double calc_new_value(double input) const {
    return (input - value_) * multiplier_ + value_;
  }
};

#endif
#ifndef _EMSTDEV_H_
#define _EMSTDEV_H_

#include <cmath>

#include "ema.h"

/**
 * @brief 指数移动标准差(Exponentially Weighted Moving Standard Deviation)
 *
 * 通过两个 EMA 分别跟踪:
 * - 均值 μₜ = EMA(xₜ)
 * - 方差 σ²ₜ = EMA( (xₜ - μₜ)² )
 *
 * 标准差 σₜ = sqrt(σ²ₜ)
 *
 * 注意:均值和方差的平滑周期可以不同,但通常建议保持一致以保证统计一致性。
 */
class EMStDev {
 public:
  /**
   * @brief 构造函数:均值和方差使用相同的平滑周期
   *
   * @param period 平滑周期(用于均值和方差)
   */
  constexpr EMStDev(unsigned int period) : _mean(period), _variance(period) {}

  /**
   * @brief 构造函数:允许均值和方差使用不同的平滑周期
   *
   * @param mean_period 均值的平滑周期
   * @param variance_period 方差的平滑周期
   */
  constexpr EMStDev(unsigned int mean_period, unsigned int variance_period)
      : _mean(mean_period), _variance(variance_period) {}

  /**
   * @brief 从已有的 EMA 对象构造
   */
  constexpr EMStDev(const EMA& mean, const EMA& variance)
      : _mean(mean), _variance(variance) {}

  /**
   * @brief 不可变更新:返回一个加入新值后的新 EMStDev 对象
   *
   * 使用更新后的均值计算偏差:(v - new_mean)^2
   */
  constexpr EMStDev operator+(double v) const {
    EMA new_mean = _mean + v;
    double deviation = v - new_mean();
    EMA new_var = _variance + deviation * deviation;
    return EMStDev(new_mean, new_var);
  }

  /**
   * @brief 原地更新:将新值 v 加入统计
   *
   * 先更新均值,再用新均值计算偏差更新方差。
   * 这是一种常用近似,保持与 operator+ 行为一致。
   */
  EMStDev& operator+=(double v) {
    _mean += v;
    double deviation = v - _mean();  // 使用更新后的均值
    _variance += deviation * deviation;
    return *this;
  }

  // 默认拷贝构造和赋值(Rule of Zero)
  EMStDev(const EMStDev&) = default;
  EMStDev& operator=(const EMStDev&) = default;

  /**
   * @brief 获取当前指数移动均值
   */
  constexpr double get_mean() const { return _mean(); }

  /**
   * @brief 获取当前指数移动标准差
   *
   * 注意:std::sqrt 不是 constexpr(C++23 前),因此本函数不能是 constexpr
   */
  double get_stdev() const { return std::sqrt(_variance()); }

  /**
   * @brief 重置初始状态
   *
   * @param mean 初始均值
   * @param stddev 初始标准差(注意:不是方差!)
   */
  void set_initial(double mean, double stddev) {
    _mean.reset(mean);
    _variance.reset(stddev * stddev);  // 转换为方差
  }

  /**
   * @brief 将标准正态变量 x 映射到当前分布:μ + σ * x
   *
   * 常用于生成符合当前统计特性的随机数或标准化逆变换。
   */
  double operator()(double x) const { return get_mean() + get_stdev() * x; }

 private:
  EMA _mean;      ///< 指数移动均值
  EMA _variance;  ///< 指数移动方差(存储的是 σ²)

  // 辅助函数:平方(可内联优化)
  static constexpr double sqr(double x) { return x * x; }
};

#endif