置换检验验证策略收益¶
置换检验通过随机重排样本标签(如交易收益的正负标记),破坏原数据中的潜在关联,生成 “无关联” 的参考分布,进而判断原数据的统计显著性。在交易系统中,常用于验证 “策略收益是否显著优于随机”:
- 计算原始策略的核心统计量(如平均收益、夏普比率)。
- 多次重排收益的 “方向标签”(如将 “盈利” 与 “亏损” 随机交换),生成大量 “随机策略” 的统计量。
- 统计原始统计量在参考分布中的分位数,判断其是否显著偏离随机水平。
#include <algorithm>
#include <cmath>
#include <iomanip>
#include <iostream>
#include <random>
#include <vector>
// 错误码定义
enum class PermTestErr {
SUCCESS = 0,
INVALID_DATA = 1, // 收益数据为空或长度<2
INVALID_PERMS = 2, // 置换次数<100(统计稳定性要求)
MEMORY_FAILED = 3 // 内存分配失败
};
class PermutationTest {
public:
/**
* @brief 构造函数:初始化置换检验器
* @param returns 原始收益数据(bar-by-bar或trade-by-trade)
* @param n_returns 收益数量
*/
PermutationTest(const double* returns, int n_returns)
: n_returns_(n_returns),
raw_returns_(nullptr),
rng_(std::random_device{}()) {
if (n_returns_ < 2) {
error_code_ = PermTestErr::INVALID_DATA;
return;
}
if (!returns) {
error_code_ = PermTestErr::INVALID_DATA;
return;
}
// 复制原始收益数据
raw_returns_ = new (std::nothrow) double[n_returns_]{};
if (!raw_returns_) {
error_code_ = PermTestErr::MEMORY_FAILED;
return;
}
memcpy(raw_returns_, returns, n_returns_ * sizeof(double));
error_code_ = PermTestErr::SUCCESS;
}
~PermutationTest() { delete[] raw_returns_; }
/**
* @brief 检查构造函数是否成功
* @return PermTestErr 错误码
*/
PermTestErr get_error_code() const { return error_code_; }
/**
* @brief 执行置换检验
* @param n_perms 置换次数(建议≥1000)
* @param stat_type 统计量类型(0=平均收益,1=夏普比率,2=盈利概率)
* @param raw_stat 输出:原始数据的统计量
* @param perm_stats 输出:所有置换样本的统计量(长度n_perms)
* @param p_value 输出:p值(原始统计量优于置换统计量的比例)
* @return PermTestErr 错误码
*/
PermTestErr run(int n_perms, int stat_type, double& raw_stat,
double* perm_stats, double& p_value) {
if (error_code_ != PermTestErr::SUCCESS) {
return error_code_;
}
if (n_perms < 100) return PermTestErr::INVALID_PERMS;
if (!perm_stats) return PermTestErr::INVALID_DATA;
// 1. 计算原始数据的统计量
if (!calc_statistic(raw_returns_, n_returns_, stat_type, raw_stat)) {
return PermTestErr::INVALID_DATA;
}
// 2. 生成置换样本并计算统计量
std::vector<double> perm_returns(n_returns_);
memcpy(perm_returns.data(), raw_returns_, n_returns_ * sizeof(double));
int count_better = 0; // 原始统计量优于置换统计量的次数
for (int p = 0; p < n_perms; ++p) {
// 重排收益标签(破坏原始关联)
std::shuffle(perm_returns.begin(), perm_returns.end(), rng_);
// 计算置换样本的统计量
double perm_stat;
if (!calc_statistic(perm_returns.data(), n_returns_, stat_type,
perm_stat)) {
return PermTestErr::INVALID_DATA;
}
perm_stats[p] = perm_stat;
// 统计原始统计量是否更优(单侧检验)
if ((stat_type == 0 && raw_stat > perm_stat) || // 平均收益:越大越优
(stat_type == 1 && raw_stat > perm_stat) || // 夏普比率:越大越优
(stat_type == 2 && raw_stat > perm_stat)) { // 盈利概率:越大越优
count_better++;
}
}
// 3. 计算p值
p_value = static_cast<double>(count_better) / n_perms;
return PermTestErr::SUCCESS;
}
private:
/**
* @brief 计算统计量
* @param returns 收益数据
* @param n 收益数量
* @param stat_type 统计量类型
* @param stat 输出:统计量值
* @return bool 是否计算成功
*/
bool calc_statistic(const double* returns, int n, int stat_type,
double& stat) {
if (n < 2) return false;
switch (stat_type) {
case 0: { // 平均收益
double sum = 0.0;
for (int i = 0; i < n; ++i) sum += returns[i];
stat = sum / n;
break;
}
case 1: { // 夏普比率(无风险利率=0)
double sum = 0.0, sum_sq = 0.0;
for (int i = 0; i < n; ++i) {
sum += returns[i];
sum_sq += returns[i] * returns[i];
}
double mean = sum / n;
double var = (sum_sq / n) - (mean * mean);
if (var < 1e-60) var = 1e-60; // 防止除以零
stat = mean / std::sqrt(var);
break;
}
case 2: { // 盈利概率(正收益次数占比)
int win_count = 0;
for (int i = 0; i < n; ++i) {
if (returns[i] > 0) win_count++;
}
stat = static_cast<double>(win_count) / n;
break;
}
default:
return false;
}
return true;
}
private:
int n_returns_; // 收益数量
double* raw_returns_; // 原始收益数据
std::mt19937 rng_; // 随机数生成器(用于重排)
PermTestErr error_code_ = PermTestErr::SUCCESS; // 错误码
};
#include "PermutationTest.h"
// 测试函数
void test_permutation_test() {
std::cout << "=== Permutation Test Implementation Test ===" << std::endl;
// 测试数据:模拟一些交易收益
double test_returns[] = {0.02, -0.01, 0.03, -0.005, 0.015,
-0.02, 0.025, 0.01, -0.01, 0.04};
int n_returns = sizeof(test_returns) / sizeof(test_returns[0]);
const int n_perms = 1000;
double perm_stats[n_perms];
double raw_stat, p_value;
// 测试1: 正常情况下的置换检验
std::cout << "\nTest 1: Normal permutation test" << std::endl;
PermutationTest pt(test_returns, n_returns);
if (pt.get_error_code() == PermTestErr::SUCCESS) {
PermTestErr err = pt.run(n_perms, 0, raw_stat, perm_stats, p_value);
if (err == PermTestErr::SUCCESS) {
std::cout << " Mean return: " << std::fixed << std::setprecision(4)
<< raw_stat << std::endl;
std::cout << " P-value: " << std::fixed << std::setprecision(4) << p_value
<< std::endl;
} else {
std::cout << " Error in permutation test: " << static_cast<int>(err)
<< std::endl;
}
} else {
std::cout << " Error creating PermutationTest: "
<< static_cast<int>(pt.get_error_code()) << std::endl;
}
// 测试2: 夏普比率统计量
std::cout << "\nTest 2: Sharpe ratio statistic" << std::endl;
PermutationTest pt2(test_returns, n_returns);
if (pt2.get_error_code() == PermTestErr::SUCCESS) {
PermTestErr err = pt2.run(n_perms, 1, raw_stat, perm_stats, p_value);
if (err == PermTestErr::SUCCESS) {
std::cout << " Sharpe ratio: " << std::fixed << std::setprecision(4)
<< raw_stat << std::endl;
std::cout << " P-value: " << std::fixed << std::setprecision(4) << p_value
<< std::endl;
} else {
std::cout << " Error in permutation test: " << static_cast<int>(err)
<< std::endl;
}
} else {
std::cout << " Error creating PermutationTest: "
<< static_cast<int>(pt2.get_error_code()) << std::endl;
}
// 测试3: 盈利概率统计量
std::cout << "\nTest 3: Win probability statistic" << std::endl;
PermutationTest pt3(test_returns, n_returns);
if (pt3.get_error_code() == PermTestErr::SUCCESS) {
PermTestErr err = pt3.run(n_perms, 2, raw_stat, perm_stats, p_value);
if (err == PermTestErr::SUCCESS) {
std::cout << " Win probability: " << std::fixed << std::setprecision(4)
<< raw_stat << std::endl;
std::cout << " P-value: " << std::fixed << std::setprecision(4) << p_value
<< std::endl;
} else {
std::cout << " Error in permutation test: " << static_cast<int>(err)
<< std::endl;
}
} else {
std::cout << " Error creating PermutationTest: "
<< static_cast<int>(pt3.get_error_code()) << std::endl;
}
// 测试4: 错误情况 - 数据过少
std::cout << "\nTest 4: Invalid data (too few returns)" << std::endl;
double small_data[] = {0.01};
PermutationTest pt4(small_data, 1);
std::cout << " Error code for insufficient data: "
<< static_cast<int>(pt4.get_error_code()) << std::endl;
// 测试5: 错误情况 - 空指针
std::cout << "\nTest 5: Invalid data (null pointer)" << std::endl;
PermutationTest pt5(nullptr, 10);
std::cout << " Error code for null data: "
<< static_cast<int>(pt5.get_error_code()) << std::endl;
// 测试6: 错误情况 - 置换次数不足
std::cout << "\nTest 6: Insufficient permutations" << std::endl;
PermutationTest pt6(test_returns, n_returns);
if (pt6.get_error_code() == PermTestErr::SUCCESS) {
PermTestErr err =
pt6.run(50, 0, raw_stat, perm_stats, p_value); // Only 50 perms < 100
std::cout << " Error code for insufficient permutations: "
<< static_cast<int>(err) << std::endl;
}
std::cout << "\n=== Test Complete ===" << std::endl;
}
int main() {
test_permutation_test();
return 0;
}