无偏交易模拟¶
无偏交易模拟的核心目标是消除 “未来数据泄露” 和 “过拟合” 对交易系统性能评估的干扰,通过模拟真实交易场景下的 “训练 - 测试” 流程,获取对系统未来表现的无偏估计。其核心原理与思想可概括为以下四点:
- 严格的时间序列数据划分逻辑 样本内(IS)与样本外(OOS)分离:必须按时间戳对市场数据(如对数价格 K 线)进行排序,确保样本内数据完全早于样本外数据,杜绝 “用未来数据训练模型” 的未来泄露。划分时需控制样本内占比(通常 0.5~0.8),且保证两者均有足够数据量(至少各 1 个有效数据点),避免因数据量不足导致的评估偏差。 守卫缓冲区(Guard Buffer)机制:针对指标回溯期(如移动平均计算所需的 K 线数)和目标前瞻期(如预测未来 N 根 K 线收益),在样本内与样本外的边界处设置 “守卫缓冲区”(大小 = min (回溯期,前瞻期)-1)。例如,若指标需回溯 20 根 K 线、目标前瞻 10 根 K 线,则缓冲区为 9 根 K 线,避免样本内数据与样本外数据因 “指标计算重叠” 导致的隐性未来泄露。
- 前向检验(Walkforward Analysis):模拟真实交易迭代流程 滑动窗口训练 - 测试:将历史数据按 “训练窗口 + 测试窗口” 划分为连续的 “折叠(Fold)”,每个折叠内先用训练窗口数据优化系统参数(如移动平均周期),再用测试窗口数据计算样本外收益,随后将整个窗口向前滑动(滑动步长 = 测试窗口大小),重复流程直至遍历所有数据。 贴合真实交易场景:每轮训练仅使用 “截至当前时间点的历史数据”,测试仅针对 “未来有限时间窗口”,完全复现真实交易中 “定期更新模型、逐步执行交易” 的过程,避免一次性用全量数据训练导致的过拟合,同时能捕捉市场非平稳性对系统性能的影响。
- 交叉验证(Cross-Validation):高效利用数据的补充评估 时间序列适配的折分逻辑:将数据按时间划分为 N 个连续的 “折(Fold)”,每个折轮流作为测试集,剩余折(排除测试集及守卫缓冲区)作为训练集。与传统交叉验证不同,需在测试集的 “前后两侧” 均设置守卫缓冲区,防止因指标回溯 / 前瞻导致的样本内 - 测试集数据重叠。 平衡数据利用率与无偏性:解决前向检验 “早期训练数据量少” 的问题,通过多轮训练 - 测试充分利用历史数据,但需注意其局限性 —— 因包含 “测试集未来的数据” 用于训练,可能对非平稳市场的评估引入轻微乐观偏差,故通常作为前向检验的补充,而非替代。
- 嵌套前向检验:处理多层优化的选择偏差 两层优化分离:针对 “系统参数优化(内层)+ 多系统选择(外层)” 的场景,需通过嵌套结构消除 “选择偏差”。内层用前向检验评估每个竞争系统(如不同参数的移动平均策略)的样本外性能,外层基于内层的无偏结果选择最优系统,再用独立的外层测试窗口验证最终性能。 避免 “幸存者偏差”:若直接从多个系统中选择 “样本内表现最好的系统”,易因 “偶然的运气成分” 高估性能;嵌套结构通过 “内层用 OOS 结果筛选系统、外层用新 OOS 结果验证”,确保选中的系统是 “真实具备泛化能力” 而非 “偶然拟合噪声” 的系统。
#include <algorithm>
#include <cassert>
#include <cmath>
#include <cstdint>
#include <iostream>
#include <numeric>
#include <vector>
// 错误码定义(无偏模拟的异常场景)
enum class SimErrorCode : int32_t {
SUCCESS = 0, // 成功
INVALID_RATIO = 1, // 样本内占比非法(≤0或≥1)
INSUFFICIENT_DATA = 2, // 数据量不足(无法满足窗口/折数要求)
EMPTY_TRAIN_DATA = 3, // 训练集为空(排除守卫缓冲区后)
EMPTY_SYSTEM_LIST = 4, // 竞争系统列表为空
INVALID_WINDOW_SIZE = 5, // 窗口大小非法(外层训练窗口超限或内层周期非法)
INVALID_MA_PARAM = 6, // 均线参数非法(短均线最大周期≥长均线最大周期)
TIMESTAMP_ORDER_ERR = 7 // 时间戳非递增(数据乱序,可能导致未来泄露)
};
// 市场数据结构体(使用对数价格确保收益尺度一致性)
struct MarketData {
double log_price; // 对数价格(用于收益计算,避免价格绝对值偏差)
int timestamp; // 时间戳(确保数据按时间顺序处理,杜绝未来泄露)
};
// 交易系统抽象类(策略核心接口,支持参数优化与收益生成)
class TradingSystem {
public:
virtual ~TradingSystem() = default;
/**
* @brief 样本内参数优化(输入样本内对数价格数据)
* @param in_sample_data 样本内市场数据(按时间戳排序)
*/
virtual void optimizeParams(
const std::vector<MarketData>& in_sample_data) = 0;
/**
* @brief 生成交易收益(输出每根K线的对数收益,无持仓时收益为0)
* @param data 输入市场数据(按时间戳排序)
* @return std::vector<double> 单根K线对数收益序列
*/
virtual std::vector<double> generateReturns(
const std::vector<MarketData>& data) const = 0;
/**
* @brief 获取最优参数(返回优化后的策略参数,用于后续分析)
* @return std::vector<double> 最优参数序列(如均线周期、阈值等)
*/
virtual std::vector<double> getOptimalParams() const = 0;
};
class UnbiasedSimulation {
public:
/**
* @brief 1. 样本内/外数据划分(按时间戳排序,避免未来泄露)
* @param full_data 完整市场数据
* @param in_sample_ratio 样本内数据占比(0 < ratio < 1)
* @param out_in_sample 输出:样本内数据(按时间戳排序)
* @param out_out_sample 输出:样本外数据(按时间戳排序)
* @return SimErrorCode 错误码
*/
static SimErrorCode splitInOutSample(
const std::vector<MarketData>& full_data, double in_sample_ratio,
std::vector<MarketData>& out_in_sample,
std::vector<MarketData>& out_out_sample) {
// 校验样本内占比(必须在(0,1)范围内,确保样本内/外均有数据)
if (in_sample_ratio <= 0.0 || in_sample_ratio >= 1.0) {
return SimErrorCode::INVALID_RATIO;
}
// 按时间戳排序(避免数据乱序导致的未来泄露)
std::vector<MarketData> sorted_data = full_data;
std::sort(sorted_data.begin(), sorted_data.end(),
[](const MarketData& a, const MarketData& b) {
return a.timestamp < b.timestamp;
});
// 校验时间戳递增(二次检查,确保排序后无乱序)
for (size_t i = 1; i < sorted_data.size(); ++i) {
if (sorted_data[i].timestamp <= sorted_data[i - 1].timestamp) {
return SimErrorCode::TIMESTAMP_ORDER_ERR;
}
}
const size_t total_size = sorted_data.size();
// 校验数据量(至少2根K线,确保样本内/外各有1根)
if (total_size < 2) {
return SimErrorCode::INSUFFICIENT_DATA;
}
// 计算划分边界(避免浮点误差,确保样本内至少1根、样本外至少1根)
size_t split_idx =
static_cast<size_t>(std::round(total_size * in_sample_ratio));
split_idx = std::max<size_t>(split_idx, 1); // 样本内至少1根
split_idx = std::min(split_idx, total_size - 1); // 样本外至少1根
// 分割数据并赋值输出
out_in_sample.assign(sorted_data.begin(), sorted_data.begin() + split_idx);
out_out_sample.assign(sorted_data.begin() + split_idx, sorted_data.end());
return SimErrorCode::SUCCESS;
}
/**
* @brief 2. 前向检验(滑动窗口模拟真实交易,生成无偏样本外收益)
* @param full_data 完整市场数据
* @param train_window_size 训练窗口大小(样本内数据量)
* @param test_window_size 测试窗口大小(样本外数据量)
* @param system 交易系统(需实现TradingSystem接口)
* @param out_oos_returns 输出:样本外收益序列(对数收益)
* @return SimErrorCode 错误码
*/
static SimErrorCode walkForwardAnalysis(
const std::vector<MarketData>& full_data, size_t train_window_size,
size_t test_window_size, TradingSystem& system,
std::vector<double>& out_oos_returns) {
// 按时间戳排序(文件基础要求,确保无未来泄露)
std::vector<MarketData> sorted_data = full_data;
std::sort(sorted_data.begin(), sorted_data.end(),
[](const MarketData& a, const MarketData& b) {
return a.timestamp < b.timestamp;
});
// 校验时间戳递增
for (size_t i = 1; i < sorted_data.size(); ++i) {
if (sorted_data[i].timestamp <= sorted_data[i - 1].timestamp) {
return SimErrorCode::TIMESTAMP_ORDER_ERR;
}
}
const size_t total_size = sorted_data.size();
// 校验窗口大小(训练+测试窗口不超过总数据量,确保每次迭代有完整窗口)
if (train_window_size == 0 || test_window_size == 0 ||
train_window_size + test_window_size > total_size) {
return SimErrorCode::INSUFFICIENT_DATA;
}
out_oos_returns.clear();
size_t current_start = 0;
// 滑动窗口迭代(批量处理逻辑,无冗余数据拷贝)
while (current_start + train_window_size + test_window_size <= total_size) {
// 步骤1:提取当前训练窗口(样本内数据)
std::vector<MarketData> train_data(
sorted_data.begin() + current_start,
sorted_data.begin() + current_start + train_window_size);
// 样本内参数优化(更新系统的最优参数)
system.optimizeParams(train_data);
// 步骤2:提取当前测试窗口(样本外数据)
std::vector<MarketData> test_data(
sorted_data.begin() + current_start + train_window_size,
sorted_data.begin() + current_start + train_window_size +
test_window_size);
// 生成样本外收益并追加到结果
std::vector<double> oos_returns = system.generateReturns(test_data);
out_oos_returns.insert(out_oos_returns.end(), oos_returns.begin(),
oos_returns.end());
// 步骤3:滑动窗口(前进测试窗口大小,避免数据重叠导致的偏差)
current_start += test_window_size;
}
return SimErrorCode::SUCCESS;
}
/**
* @brief 3. 交叉验证(高效利用数据,处理时间序列IS/OOS重叠)
* @param full_data 完整市场数据
* @param n_folds 折数(≥2,确保训练/测试集划分有效)
* @param lookback 指标回溯期(如均线周期,用于计算守卫缓冲区)
* @param lookahead 目标前瞻期(如持仓周期,用于计算守卫缓冲区)
* @param system 交易系统(需实现TradingSystem接口)
* @param out_oos_returns 输出:样本外收益序列(对数收益)
* @return SimErrorCode 错误码
*/
static SimErrorCode crossValidation(const std::vector<MarketData>& full_data,
size_t n_folds, size_t lookback,
size_t lookahead, TradingSystem& system,
std::vector<double>& out_oos_returns) {
// 按时间戳排序(避免未来泄露)
std::vector<MarketData> sorted_data = full_data;
std::sort(sorted_data.begin(), sorted_data.end(),
[](const MarketData& a, const MarketData& b) {
return a.timestamp < b.timestamp;
});
// 校验时间戳递增
for (size_t i = 1; i < sorted_data.size(); ++i) {
if (sorted_data[i].timestamp <= sorted_data[i - 1].timestamp) {
return SimErrorCode::TIMESTAMP_ORDER_ERR;
}
}
const size_t total_size = sorted_data.size();
// 校验折数和数据量(折数≥2,数据量≥折数,确保每折有足够数据)
if (n_folds < 2 || total_size < n_folds) {
return SimErrorCode::INSUFFICIENT_DATA;
}
// 计算守卫缓冲区(核心逻辑:避免IS/OOS重叠,取min(lookback, lookahead)-1)
const size_t guard_size = (std::min(lookback, lookahead) > 0)
? (std::min(lookback, lookahead) - 1)
: 0;
const size_t fold_size = total_size / n_folds; // 每折的基础大小
out_oos_returns.clear();
// 迭代每个折作为测试集
for (size_t fold_idx = 0; fold_idx < n_folds; ++fold_idx) {
// 计算当前折的测试集范围
const size_t test_start = fold_idx * fold_size;
const size_t test_end =
(fold_idx == n_folds - 1) ? total_size : (fold_idx + 1) * fold_size;
// 构建训练集(排除测试集及守卫缓冲区,避免IS/OOS重叠)
std::vector<MarketData> train_data;
// 测试集左侧的训练数据(排除左侧守卫缓冲区)
if (test_start > guard_size) {
train_data.insert(train_data.end(), sorted_data.begin(),
sorted_data.begin() + test_start - guard_size);
}
// 测试集右侧的训练数据(排除右侧守卫缓冲区)
if (test_end + guard_size < total_size) {
train_data.insert(train_data.end(),
sorted_data.begin() + test_end + guard_size,
sorted_data.end());
}
// 校验训练集非空(无训练数据无法优化参数)
if (train_data.empty()) {
return SimErrorCode::EMPTY_TRAIN_DATA;
}
// 步骤1:样本内参数优化
system.optimizeParams(train_data);
// 步骤2:提取当前折的测试集
std::vector<MarketData> test_data(sorted_data.begin() + test_start,
sorted_data.begin() + test_end);
// 步骤3:生成样本外收益并追加
std::vector<double> oos_returns = system.generateReturns(test_data);
out_oos_returns.insert(out_oos_returns.end(), oos_returns.begin(),
oos_returns.end());
}
return SimErrorCode::SUCCESS;
}
/**
* @brief 4. 嵌套前向检验(处理两层优化:系统选择+参数优化)
* @param full_data 完整市场数据
* @param inner_train_window 内层训练窗口大小(系统参数优化用)
* @param inner_test_window 内层测试窗口大小(系统性能评估用)
* @param outer_train_window 外层训练窗口大小(系统选择用)
* @param systems 竞争系统列表(需实现TradingSystem接口)
* @param out_final_returns 输出:最终样本外收益序列(对数收益)
* @return SimErrorCode 错误码
*/
static SimErrorCode nestedWalkForward(
const std::vector<MarketData>& full_data, size_t inner_train_window,
size_t inner_test_window, size_t outer_train_window,
const std::vector<TradingSystem*>& systems,
std::vector<double>& out_final_returns) {
// 校验竞争系统列表非空(无系统无法进行选择)
if (systems.empty()) {
return SimErrorCode::EMPTY_SYSTEM_LIST;
}
// 按时间戳排序(文件要求,避免未来泄露)
std::vector<MarketData> sorted_data = full_data;
std::sort(sorted_data.begin(), sorted_data.end(),
[](const MarketData& a, const MarketData& b) {
return a.timestamp < b.timestamp;
});
// 校验时间戳递增
for (size_t i = 1; i < sorted_data.size(); ++i) {
if (sorted_data[i].timestamp <= sorted_data[i - 1].timestamp) {
return SimErrorCode::TIMESTAMP_ORDER_ERR;
}
}
const size_t total_size = sorted_data.size();
const size_t inner_cycle =
inner_train_window + inner_test_window; // 内层前向检验周期
// 校验窗口大小(外层训练窗口≤总数据量,内层周期≤外层训练窗口)
if (outer_train_window == 0 || inner_train_window == 0 ||
inner_test_window == 0 || outer_train_window > total_size ||
inner_cycle > outer_train_window) {
return SimErrorCode::INVALID_WINDOW_SIZE;
}
out_final_returns.clear();
size_t outer_current_start = 0;
// 外层前向检验(迭代选择最优系统)
while (outer_current_start + outer_train_window + inner_test_window <=
total_size) {
// 步骤1:提取外层训练数据(用于系统选择)
std::vector<MarketData> outer_train_data(
sorted_data.begin() + outer_current_start,
sorted_data.begin() + outer_current_start + outer_train_window);
// 步骤2:内层前向检验:评估每个系统的性能得分
std::vector<double> system_scores(systems.size(),
-std::numeric_limits<double>::max());
for (size_t sys_idx = 0; sys_idx < systems.size(); ++sys_idx) {
std::vector<double> inner_returns;
// 调用内层前向检验,生成系统的样本外收益
SimErrorCode err = walkForwardAnalysis(
outer_train_data, inner_train_window, inner_test_window,
*systems[sys_idx], inner_returns);
if (err != SimErrorCode::SUCCESS) {
continue; // 跳过评估失败的系统
}
// 计算系统得分(总对数收益,常用的评估指标)
system_scores[sys_idx] =
std::accumulate(inner_returns.begin(), inner_returns.end(), 0.0);
}
// 步骤3:选择最优系统(得分最高的系统)
auto best_sys_iter =
std::max_element(system_scores.begin(), system_scores.end());
if (*best_sys_iter == -std::numeric_limits<double>::max()) {
return SimErrorCode::EMPTY_SYSTEM_LIST; // 所有系统评估失败
}
size_t best_sys_idx = best_sys_iter - system_scores.begin();
TradingSystem* best_sys = systems[best_sys_idx];
// 步骤4:外层测试:生成最终样本外收益
std::vector<MarketData> outer_test_data(
sorted_data.begin() + outer_current_start + outer_train_window,
sorted_data.begin() + outer_current_start + outer_train_window +
inner_test_window);
std::vector<double> oos_returns =
best_sys->generateReturns(outer_test_data);
out_final_returns.insert(out_final_returns.end(), oos_returns.begin(),
oos_returns.end());
// 步骤5:滑动外层窗口(前进内层测试窗口大小)
outer_current_start += inner_test_window;
}
return SimErrorCode::SUCCESS;
}
};
#include "UnbiasedSimulation.h"
// 移动平均交叉策略(示例策略,经典均线交叉逻辑)
class MACrossSystem : public TradingSystem {
public:
/**
* @brief 构造函数:初始化短/长期均线最大周期
* @param max_short_lookback 短均线最大周期(如1~20)
* @param max_long_lookback 长均线最大周期(如2~40)
*/
MACrossSystem(size_t max_short_lookback, size_t max_long_lookback)
: max_short_lookback_(max_short_lookback),
max_long_lookback_(max_long_lookback),
opt_short_lookback_(1),
opt_long_lookback_(2) {}
/**
* @brief 校验均线参数合法性(短均线最大周期必须<长均线最大周期)
* @return SimErrorCode 错误码
*/
SimErrorCode validateParams() const {
if (max_short_lookback_ >= max_long_lookback_) {
return SimErrorCode::INVALID_MA_PARAM;
}
return SimErrorCode::SUCCESS;
}
/**
* @brief 样本内参数优化:遍历均线组合,选择总对数收益最大的组合
* @param in_sample_data 样本内市场数据(按时间戳排序)
*/
void optimizeParams(const std::vector<MarketData>& in_sample_data) override {
const size_t data_size = in_sample_data.size();
// 数据量不足时不优化(需至少长均线最大周期的K线)
if (data_size < max_long_lookback_) {
return;
}
double best_total_ret = -std::numeric_limits<double>::max();
// 遍历所有合法均线组合(短均线周期 < 长均线周期)
for (size_t short_lb = 1; short_lb <= max_short_lookback_; ++short_lb) {
for (size_t long_lb = short_lb + 1; long_lb <= max_long_lookback_;
++long_lb) {
// 计算当前均线组合的总对数收益
double current_ret =
calculateTotalReturn(in_sample_data, short_lb, long_lb);
// 更新最优参数(总收益更大时)
if (current_ret > best_total_ret) {
best_total_ret = current_ret;
opt_short_lookback_ = short_lb;
opt_long_lookback_ = long_lb;
}
}
}
}
/**
* @brief 生成收益:基于最优均线周期计算单根K线对数收益
* @param data 输入市场数据(按时间戳排序)
* @return std::vector<double> 单根K线对数收益序列
*/
std::vector<double> generateReturns(
const std::vector<MarketData>& data) const override {
return calculateReturnDetail(data, opt_short_lookback_, opt_long_lookback_);
}
/**
* @brief 获取最优参数:短/长期均线周期
* @return std::vector<double> 最优参数(短均线周期、长均线周期)
*/
std::vector<double> getOptimalParams() const override {
return {static_cast<double>(opt_short_lookback_),
static_cast<double>(opt_long_lookback_)};
}
private:
size_t max_short_lookback_; // 短均线最大周期
size_t max_long_lookback_; // 长均线最大周期
size_t opt_short_lookback_; // 优化后的短均线周期
size_t opt_long_lookback_; // 优化后的长均线周期
/**
* @brief 计算总对数收益(用于参数优化)
* @param data 输入市场数据(按时间戳排序)
* @param short_lb 短均线周期
* @param long_lb 长均线周期
* @return double 总对数收益
*/
double calculateTotalReturn(const std::vector<MarketData>& data,
size_t short_lb, size_t long_lb) const {
double total_ret = 0.0;
const size_t data_size = data.size();
// 从长均线周期后开始计算(确保能完整计算两条均线)
for (size_t i = long_lb; i < data_size - 1; ++i) {
// 计算短/长期均线(对数价格均值,避免价格尺度偏差)
double short_ma = 0.0, long_ma = 0.0;
for (size_t j = 0; j < short_lb; ++j) {
short_ma += data[i - j].log_price;
}
for (size_t j = 0; j < long_lb; ++j) {
long_ma += data[i - j].log_price;
}
short_ma /= short_lb;
long_ma /= long_lb;
// 生成交易信号与收益(短均线上穿做多,下穿做空,无交叉无持仓)
double ret = 0.0;
if (short_ma > long_ma) {
// 多头收益:下一根K线对数价格 - 当前K线对数价格
ret = data[i + 1].log_price - data[i].log_price;
} else if (short_ma < long_ma) {
// 空头收益:当前K线对数价格 - 下一根K线对数价格
ret = data[i].log_price - data[i + 1].log_price;
}
total_ret += ret;
}
return total_ret;
}
/**
* @brief 计算详细收益(用于样本外测试,返回单根K线收益)
* @param data 输入市场数据(按时间戳排序)
* @param short_lb 短均线周期
* @param long_lb 长均线周期
* @return std::vector<double> 单根K线对数收益序列
*/
std::vector<double> calculateReturnDetail(const std::vector<MarketData>& data,
size_t short_lb,
size_t long_lb) const {
std::vector<double> returns;
const size_t data_size = data.size();
// 预分配内存,提升性能(收益数量 = 数据量 - 长均线周期)
returns.reserve(data_size - long_lb);
for (size_t i = long_lb; i < data_size - 1; ++i) {
// 计算短/长期均线
double short_ma = 0.0, long_ma = 0.0;
for (size_t j = 0; j < short_lb; ++j) {
short_ma += data[i - j].log_price;
}
for (size_t j = 0; j < long_lb; ++j) {
long_ma += data[i - j].log_price;
}
short_ma /= short_lb;
long_ma /= long_lb;
// 计算单根K线收益
double ret = 0.0;
if (short_ma > long_ma) {
ret = data[i + 1].log_price - data[i].log_price;
} else if (short_ma < long_ma) {
ret = data[i].log_price - data[i + 1].log_price;
}
returns.push_back(ret);
}
return returns;
}
};
// 测试代码实现
void testSplitInOutSample() {
std::cout << "Testing splitInOutSample..." << std::endl;
// 测试正常情况
std::vector<MarketData> data = {
{1.0, 1}, {2.0, 2}, {3.0, 3}, {4.0, 4}, {5.0, 5}};
std::vector<MarketData> in_sample, out_sample;
auto result =
UnbiasedSimulation::splitInOutSample(data, 0.6, in_sample, out_sample);
assert(result == SimErrorCode::SUCCESS);
assert(in_sample.size() == 3);
assert(out_sample.size() == 2);
assert(in_sample[0].timestamp < out_sample[0].timestamp);
// 测试无效比例
result =
UnbiasedSimulation::splitInOutSample(data, 1.5, in_sample, out_sample);
assert(result == SimErrorCode::INVALID_RATIO);
result =
UnbiasedSimulation::splitInOutSample(data, -0.1, in_sample, out_sample);
assert(result == SimErrorCode::INVALID_RATIO);
// 测试数据量不足
std::vector<MarketData> small_data = {{1.0, 1}};
result = UnbiasedSimulation::splitInOutSample(small_data, 0.5, in_sample,
out_sample);
assert(result == SimErrorCode::INSUFFICIENT_DATA);
// 测试时间戳非递增
std::vector<MarketData> unordered_data = {{1.0, 2}, {2.0, 1}};
result = UnbiasedSimulation::splitInOutSample(unordered_data, 0.5, in_sample,
out_sample);
assert(result == SimErrorCode::TIMESTAMP_ORDER_ERR);
std::cout << "splitInOutSample tests passed." << std::endl;
}
void testWalkForwardAnalysis() {
std::cout << "Testing walkForwardAnalysis..." << std::endl;
// 创建测试数据
std::vector<MarketData> data;
for (int i = 1; i <= 20; ++i) {
data.push_back({static_cast<double>(i), i});
}
// 创建交易系统
MACrossSystem system(3, 5);
std::vector<double> oos_returns;
auto result =
UnbiasedSimulation::walkForwardAnalysis(data, 10, 5, system, oos_returns);
assert(result == SimErrorCode::SUCCESS);
// 测试窗口大小非法
result =
UnbiasedSimulation::walkForwardAnalysis(data, 0, 5, system, oos_returns);
assert(result == SimErrorCode::INSUFFICIENT_DATA);
result = UnbiasedSimulation::walkForwardAnalysis(data, 15, 10, system,
oos_returns);
assert(result == SimErrorCode::INSUFFICIENT_DATA);
// 测试时间戳非递增
std::vector<MarketData> unordered_data = {{1.0, 2}, {2.0, 1}};
result = UnbiasedSimulation::walkForwardAnalysis(unordered_data, 1, 1, system,
oos_returns);
assert(result == SimErrorCode::TIMESTAMP_ORDER_ERR);
std::cout << "walkForwardAnalysis tests passed." << std::endl;
}
void testCrossValidation() {
std::cout << "Testing crossValidation..." << std::endl;
// 创建测试数据
std::vector<MarketData> data;
for (int i = 1; i <= 20; ++i) {
data.push_back({static_cast<double>(i), i});
}
MACrossSystem system(2, 4);
std::vector<double> oos_returns;
auto result =
UnbiasedSimulation::crossValidation(data, 4, 2, 1, system, oos_returns);
assert(result == SimErrorCode::SUCCESS);
// 测试折数非法
result =
UnbiasedSimulation::crossValidation(data, 1, 2, 1, system, oos_returns);
assert(result == SimErrorCode::INSUFFICIENT_DATA);
// 测试数据量不足
std::vector<MarketData> small_data = {{1.0, 1}, {2.0, 2}};
result = UnbiasedSimulation::crossValidation(small_data, 3, 1, 1, system,
oos_returns);
assert(result == SimErrorCode::INSUFFICIENT_DATA);
// 测试时间戳非递增
std::vector<MarketData> unordered_data = {{1.0, 2}, {2.0, 1}};
result = UnbiasedSimulation::crossValidation(unordered_data, 2, 1, 1, system,
oos_returns);
assert(result == SimErrorCode::TIMESTAMP_ORDER_ERR);
std::cout << "crossValidation tests passed." << std::endl;
}
void testNestedWalkForward() {
std::cout << "Testing nestedWalkForward..." << std::endl;
// 创建测试数据
std::vector<MarketData> data;
for (int i = 1; i <= 30; ++i) {
data.push_back({static_cast<double>(i), i});
}
// 创建多个交易系统
MACrossSystem system1(2, 4);
MACrossSystem system2(3, 6);
std::vector<TradingSystem*> systems = {&system1, &system2};
std::vector<double> final_returns;
auto result = UnbiasedSimulation::nestedWalkForward(data, 8, 4, 12, systems,
final_returns);
assert(result == SimErrorCode::SUCCESS);
// 测试系统列表为空
std::vector<TradingSystem*> empty_systems;
result = UnbiasedSimulation::nestedWalkForward(data, 5, 3, 10, empty_systems,
final_returns);
assert(result == SimErrorCode::EMPTY_SYSTEM_LIST);
// 测试窗口大小非法
result = UnbiasedSimulation::nestedWalkForward(data, 0, 3, 10, systems,
final_returns);
assert(result == SimErrorCode::INVALID_WINDOW_SIZE);
result = UnbiasedSimulation::nestedWalkForward(data, 5, 3, 0, systems,
final_returns);
assert(result == SimErrorCode::INVALID_WINDOW_SIZE);
// 测试时间戳非递增
std::vector<MarketData> unordered_data = {{1.0, 2}, {2.0, 1}};
result = UnbiasedSimulation::nestedWalkForward(unordered_data, 1, 1, 1,
systems, final_returns);
assert(result == SimErrorCode::TIMESTAMP_ORDER_ERR);
std::cout << "nestedWalkForward tests passed." << std::endl;
}
void testMACrossSystem() {
std::cout << "Testing MACrossSystem..." << std::endl;
// 测试参数合法性
MACrossSystem valid_system(2, 5);
assert(valid_system.validateParams() == SimErrorCode::SUCCESS);
MACrossSystem invalid_system(5, 3);
assert(invalid_system.validateParams() == SimErrorCode::INVALID_MA_PARAM);
// 测试优化参数
std::vector<MarketData> data;
for (int i = 1; i <= 10; ++i) {
data.push_back({static_cast<double>(i), i});
}
valid_system.optimizeParams(data);
auto params = valid_system.getOptimalParams();
assert(params.size() == 2);
// 测试生成收益
auto returns = valid_system.generateReturns(data);
assert(returns.size() == data.size() - 5); // 长周期为5
std::cout << "MACrossSystem tests passed." << std::endl;
}
int main() {
std::cout << "Starting tests..." << std::endl;
testSplitInOutSample();
testWalkForwardAnalysis();
testCrossValidation();
testNestedWalkForward();
testMACrossSystem();
std::cout << "All tests passed!" << std::endl;
return 0;
}