参数敏感性曲线¶
参数敏感性曲线 (ParamSensitivity Class)
目标:直观地验证单个参数在整个可行域内扰动时,策略性能的稳定性。
核心原理:
单参数扰动: 从最优参数集开始,每次只选择一个参数,将其值从下限到上限均匀(或线性)地进行步进式变化。
保持其他参数不变: 在评估性能时,所有其他参数都固定在它们的最优值上。
性能评估: 对每个扰动点调用策略的性能评估函数(trade_system_criter)。
曲线解读: 最终生成的性能曲线(通常以文本直方图形式呈现)的形状决定了参数的稳健性:
平坦的曲线: 表明该参数在较大范围内变化时,性能保持稳定,参数鲁棒性高。
尖锐的峰值: 表明策略对该参数的取值非常敏感,可能存在过度拟合风险。
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <vector>
// 错误码定义(与ParamCorrelation保持一致)
enum PostOptErr {
POST_OPT_SUCCESS = 0,
POST_OPT_EMPTY_DATA = 1,
POST_OPT_NULL_PTR = 2,
POST_OPT_MEM_ALLOC = 3,
POST_OPT_INVALID_PARAM = 4
};
/**
* @brief 性能评估函数类型定义
* @param params 输入参数集(长度n_params)
* @param min_trades 最小交易数约束(低于此值返回负性能)
* @return double 性能值(如总收益、夏普比率等)
*/
typedef double (*PerformanceCriter)(const double* params, int min_trades);
class ParamSensitivity {
private:
int n_params; // 优化参数总数
int n_int_params; // 整数参数数量(前n_int_params个参数为整数)
double* best_params; // 优化后的最优参数集(长度n_params)
double* low_bounds; // 参数下界(长度n_params)
double* high_bounds; // 参数上界(长度n_params)
PerformanceCriter criter; // 性能评估函数
int min_trades; // 最小交易数约束(性能评估时使用)
public:
/**
* @brief 构造函数:初始化参数敏感性分析器
* @param n_params 优化参数总数
* @param n_int_params 整数参数数量(前n_int_params个)
* @param best_params 优化后的最优参数集(长度n_params)
* @param low_bounds 参数下界(长度n_params)
* @param high_bounds 参数上界(长度n_params)
* @param criter 性能评估函数
* @param min_trades 最小交易数约束
* @return PostOptErr 错误码
*/
int init(int n_params, int n_int_params, const double* best_params,
const double* low_bounds, const double* high_bounds,
PerformanceCriter criter, int min_trades) {
// 输入合法性检查
if (n_params <= 0 || n_int_params < 0 || n_int_params > n_params)
return POST_OPT_INVALID_PARAM;
if (!best_params || !low_bounds || !high_bounds || !criter)
return POST_OPT_NULL_PTR;
this->n_params = n_params;
this->n_int_params = n_int_params;
this->min_trades = min_trades;
this->criter = criter;
// 内存分配
this->best_params = new (std::nothrow) double[n_params]();
this->low_bounds = new (std::nothrow) double[n_params]();
this->high_bounds = new (std::nothrow) double[n_params]();
// 内存分配检查
if (!this->best_params || !this->low_bounds || !this->high_bounds) {
if (this->best_params) delete[] this->best_params;
if (this->low_bounds) delete[] this->low_bounds;
if (this->high_bounds) delete[] this->high_bounds;
this->best_params = nullptr;
this->low_bounds = nullptr;
this->high_bounds = nullptr;
return POST_OPT_MEM_ALLOC;
}
// 复制输入数据
memcpy(this->best_params, best_params, n_params * sizeof(double));
memcpy(this->low_bounds, low_bounds, n_params * sizeof(double));
memcpy(this->high_bounds, high_bounds, n_params * sizeof(double));
return POST_OPT_SUCCESS;
}
/**
* @brief 析构函数:释放内存
*/
~ParamSensitivity() {
delete[] best_params;
delete[] low_bounds;
delete[] high_bounds;
}
/**
* @brief 生成单个参数的敏感性曲线(文本直方图形式)
* @param param_idx 目标参数索引(0~n_params-1)
* @param n_points 扰动点数量(建议≥5,确保曲线平滑)
* @param n_res 直方图分辨率(最大星号数量)
* @param log_path 日志文件路径(追加模式写入)
* @return PostOptErr 错误码
*/
int generate_curve(int param_idx, int n_points, int n_res,
const char* log_path) {
// 输入合法性检查
if (param_idx < 0 || param_idx >= n_params) return POST_OPT_INVALID_PARAM;
if (n_points < 5 || n_res < 10)
return POST_OPT_INVALID_PARAM; // 点数过少或分辨率过低
if (!log_path || !criter) return POST_OPT_NULL_PTR;
// 打开日志文件(追加模式)
FILE* fp = fopen(log_path, "at");
if (!fp) return POST_OPT_NULL_PTR;
// 分配临时内存:参数缓冲区与性能缓冲区
double* params = new (std::nothrow) double[n_params]();
double* perf = new (std::nothrow) double[n_points]();
if (!params || !perf) {
delete[] params;
delete[] perf;
fclose(fp);
return POST_OPT_MEM_ALLOC;
}
// ---------------------------------------------------------------------
// 步骤1:初始化参数(仅扰动目标参数,其他参数保持最优值)
// ---------------------------------------------------------------------
memcpy(params, best_params, n_params * sizeof(double));
double param_min = low_bounds[param_idx];
double param_max = high_bounds[param_idx];
double step = (param_max - param_min) / (n_points - 1); // 扰动步长
double max_perf = -1e60; // 最大性能值(用于直方图缩放)
// ---------------------------------------------------------------------
// 步骤2:遍历所有扰动点,计算性能
// ---------------------------------------------------------------------
for (int i = 0; i < n_points; ++i) {
// 设置当前参数值(区分整数/实数)
if (param_idx < n_int_params) {
// 整数参数:四舍五入到最近整数,并确保在[min, max]范围内
double curr_val = param_min + i * step;
curr_val = std::round(curr_val);
curr_val = std::max(param_min, std::min(param_max, curr_val));
params[param_idx] = curr_val;
} else {
// 实数参数:线性插值,直接赋值
params[param_idx] = param_min + i * step;
}
// 评估性能(调用外部性能函数)
perf[i] = criter(params, min_trades);
// 更新最大性能值(用于后续直方图缩放)
if (perf[i] > max_perf) max_perf = perf[i];
}
// ---------------------------------------------------------------------
// 步骤3:生成文本直方图并写入日志
// ---------------------------------------------------------------------
fprintf(fp, "\n=== 参数敏感性曲线(参数%d) ===\n", param_idx + 1);
fprintf(fp, "参数类型: %s\n", (param_idx < n_int_params) ? "整数" : "实数");
fprintf(fp, "最优值: %.4f\n", best_params[param_idx]);
fprintf(fp, "扰动范围: [%.4f, %.4f](%d个点)\n", param_min, param_max,
n_points);
fprintf(fp, "直方图分辨率: %d个星号(*)对应最大性能%.6f\n", n_res,
max_perf);
fprintf(fp, "----------------------------------------\n");
// 计算直方图缩放比例(性能→星号数量)
double hist_scale =
(max_perf > 1e-10) ? (static_cast<double>(n_res) / max_perf) : 1.0;
// 遍历每个扰动点,打印参数值与直方图
for (int i = 0; i < n_points; ++i) {
double curr_val = 0.0;
// 获取当前参数值(区分整数/实数)
if (param_idx < n_int_params) {
curr_val = param_min + i * step;
curr_val = std::round(curr_val);
curr_val = std::max(param_min, std::min(param_max, curr_val));
fprintf(fp, "%8d|", static_cast<int>(curr_val)); // 整数格式输出
} else {
curr_val = param_min + i * step;
fprintf(fp, "%10.3f|", curr_val); // 实数格式输出(保留3位小数)
}
// 计算星号数量(性能×缩放比例,限制在[0, n_res])
int n_stars = static_cast<int>(perf[i] * hist_scale);
n_stars = std::max(0, std::min(n_res, n_stars));
// 打印星号
for (int j = 0; j < n_stars; ++j) {
fprintf(fp, "*");
}
fprintf(fp, "\n");
}
fprintf(fp, "----------------------------------------\n");
// ---------------------------------------------------------------------
// 清理临时内存并关闭文件
// ---------------------------------------------------------------------
delete[] params;
delete[] perf;
fclose(fp);
return POST_OPT_SUCCESS;
}
/**
* @brief 批量生成所有参数的敏感性曲线
* @param n_points 每个参数的扰动点数量(建议≥5)
* @param n_res 直方图分辨率(最大星号数量,建议≥20)
* @param log_path 日志文件路径(覆盖模式写入)
* @return PostOptErr 错误码
*/
int generate_all_curves(int n_points, int n_res, const char* log_path) {
// 输入合法性检查
if (!log_path || !criter) return POST_OPT_NULL_PTR;
if (n_points < 5 || n_res < 10) return POST_OPT_INVALID_PARAM;
// 先创建新日志文件(覆盖原有内容)
FILE* fp = fopen(log_path, "wt");
if (!fp) return POST_OPT_NULL_PTR;
fprintf(fp, "=== 所有参数敏感性曲线 ===\n");
fprintf(fp, "参数总数: %d(整数参数: %d,实数参数: %d)\n", n_params,
n_int_params, n_params - n_int_params);
fprintf(fp, "每个参数扰动点数量: %d\n", n_points);
fprintf(fp, "直方图分辨率: %d个星号\n", n_res);
fprintf(fp, "最小交易数约束: %d\n\n", min_trades);
fclose(fp);
// 逐个参数生成敏感性曲线
for (int i = 0; i < n_params; ++i) {
int err = generate_curve(i, n_points, n_res, log_path);
if (err != POST_OPT_SUCCESS) {
return err; // 遇到错误时终止并返回错误码
}
}
return POST_OPT_SUCCESS;
}
};
#include "ParamSensitivity.h"
// 测试用的性能评估函数
double test_performance_function(const double* params, int min_trades) {
// 简单示例:返回参数的平方和作为性能指标
double sum = 0.0;
for (int i = 0; i < 3; i++) {
sum += params[i] * params[i];
}
// 如果交易数不足,返回负值
if (min_trades < 10) {
return -1.0;
}
return 100.0 - sum; // 最优值在参数为0时取得
}
// 测试函数
int test_param_sensitivity() {
// 测试数据
const int n_params = 3;
const int n_int_params = 1;
double best_params[n_params] = {0.0, 0.5, -0.3};
double low_bounds[n_params] = {-5.0, -2.0, -1.0};
double high_bounds[n_params] = {5.0, 2.0, 1.0};
// 创建ParamSensitivity对象
ParamSensitivity sensitivity;
int result = sensitivity.init(n_params, n_int_params, best_params, low_bounds,
high_bounds, test_performance_function, 15);
if (result != POST_OPT_SUCCESS) {
printf("初始化失败,错误码: %d\n", result);
return result;
}
// 测试生成单个参数曲线
result = sensitivity.generate_curve(0, 10, 20, "sensitivity_test.log");
if (result != POST_OPT_SUCCESS) {
printf("生成单个参数曲线失败,错误码: %d\n", result);
return result;
}
// 测试生成所有参数曲线
result = sensitivity.generate_all_curves(8, 15, "sensitivity_all_test.log");
if (result != POST_OPT_SUCCESS) {
printf("生成所有参数曲线失败,错误码: %d\n", result);
return result;
}
printf("所有测试通过\n");
return POST_OPT_SUCCESS;
}
// 主函数用于运行测试
int main() {
int result = test_param_sensitivity();
if (result == POST_OPT_SUCCESS) {
printf("ParamSensitivity测试成功完成\n");
} else {
printf("ParamSensitivity测试失败,错误码: %d\n", result);
}
return result;
}