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