跳转至

交易指标评估的信息熵

1. 用“熵”量化指标信息价值

  • 交易指标(如趋势、波动率)可视为市场传递的“信息”,其价值取决于消除市场未来状态不确定性的能力,这一能力用“熵”衡量。
  • 计算指标相对熵(实际熵值/最大可能熵值),将信息含量标准化到0-1范围:0表示无信息,1表示信息最大化,以此作为指标价值的核心评判标准,通常要求相对熵至少0.5。
  • 分箱是计算熵的关键操作:需将指标数值范围按等间隔分成多个区间(如20个),统计每个区间的指标值占比,再代入香农熵公式(H = -Σp(x)·log(p(x)))计算,避免等数量分箱导致的熵值失真。

2. 低熵指标的问题与优化方法

  • 低熵的危害:低熵指标(如含极端异常值、数值过度聚类)会干扰模型训练,例如异常值会让模型过度关注少数数据,忽略多数正常样本;数值聚类会导致模型误将无关聚类差异当作核心规律,降低预测准确性。
  • 优化手段:
  • 尾部清洗:通过排序找到指标的核心数据区间(覆盖大部分正常数据),对区间外的极端值进行单调压缩(如指数函数压缩),既保留数据顺序,又避免异常值破坏分布,提升熵值。
  • 非线性变换:针对不同分布缺陷选择对应变换,如平方根/立方根处理偏态分布、双曲正切/逻辑函数压缩极端值、累积分布函数匹配标准分布,优化指标数值分布以提高信息含量。

3. 熵与交易模型的关联

  • 高熵指标通常对应数值在范围内均匀分布,这种分布能让预测模型更高效学习数据规律,减少异常值或聚类带来的干扰,因此熵值高低与模型训练效果高度相关。
  • 需注意熵的局限性:高熵仅代表指标信息含量高,需进一步验证信息是否与交易目标(如判断多空、预测波动率)相关;少数情况下优化熵可能降低指标实用性,但属于例外情况,多数时候熵提升会改善模型性能。
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <cstring>
#include <vector>

// 错误码定义
enum EntropyCalcErr {
  ERR_SUCCESS = 0,
  ERR_EMPTY_DATA = 1,
  ERR_INVALID_BINS = 2,
  ERR_INVALID_TAIL_FRAC = 3,
  ERR_NULL_PTR = 4
};

// 熵计算与指标优化工具类
class TradingIndicatorEntropy {
 public:
  // 1. 计算指标的相对熵(核心分箱算法)
  // 参数:数据长度n、数据指针x、分箱数nbins、结果输出指针rel_entropy
  static int CalculateRelativeEntropy(int n, const double* x, int nbins,
                                      double* rel_entropy) {
    // 输入合法性检查
    if (x == nullptr || rel_entropy == nullptr) return ERR_NULL_PTR;
    if (n <= 0) return ERR_EMPTY_DATA;
    if (nbins < 2) return ERR_INVALID_BINS;

    // 步骤1:计算数据的最小值和最大值
    double min_val = x[0], max_val = x[0];
    for (int i = 1; i < n; ++i) {
      if (x[i] < min_val) min_val = x[i];
      if (x[i] > max_val) max_val = x[i];
    }

    // 步骤2:计算分箱映射因子(避免除以零和越界)
    const double denom = (max_val - min_val) + 1e-60;  // 防止所有数据相等
    const double factor = (nbins - 1e-10) / denom;     // 防止映射到不存在的分箱

    // 步骤3:统计每个分箱的计数
    int* bin_counts = new int[nbins]();  // 初始化全0
    for (int i = 0; i < n; ++i) {
      const int bin_idx = static_cast<int>(factor * (x[i] - min_val));
      bin_counts[bin_idx]++;
    }

    // 步骤4:计算香农熵并归一化为相对熵
    double entropy = 0.0;
    const double n_inv = 1.0 / static_cast<double>(n);
    const double log_nbins = log(static_cast<double>(nbins));

    for (int i = 0; i < nbins; ++i) {
      if (bin_counts[i] == 0) continue;  // 0*log(0)定义为0,跳过

      const double p = static_cast<double>(bin_counts[i]) * n_inv;
      entropy -= p * log(p);
    }

    // 计算相对熵(H(X)/log(K))
    *rel_entropy = entropy / log_nbins;

    // 释放内存
    delete[] bin_counts;
    return ERR_SUCCESS;
  }

  // 2. 单调尾部清洗(仅处理极端值)
  // 参数:数据长度n、原始数据指针raw(会被修改)、工作区指针work(需提前分配n个元素空间)
  static int CleanTails(int n, double* raw, double* work, double tail_frac) {
    // 输入合法性检查
    if (raw == nullptr || work == nullptr) return ERR_NULL_PTR;
    if (n <= 0) return ERR_EMPTY_DATA;
    if (tail_frac <= 0.0 || tail_frac >= 0.5) return ERR_INVALID_TAIL_FRAC;

    // 步骤1:复制数据到工作区并排序
    memcpy(work, raw, sizeof(double) * n);
    std::sort(work, work + n);

    // 步骤2:确定核心数据区间(覆盖cover比例的数据,找范围最小的区间)
    const double cover = 1.0 - 2.0 * tail_frac;
    int istop = static_cast<int>(cover * (n + 1)) - 1;
    istop = (istop >= n) ? (n - 1) : istop;
    int istart = 0;

    double min_range = 1e60;
    int best_start = 0, best_stop = 0;

    while (istop < n) {
      const double range = work[istop] - work[istart];
      if (range < min_range) {
        min_range = range;
        best_start = istart;
        best_stop = istop;
      }
      istart++;
      istop++;
    }

    // 步骤3:获取核心区间的边界(避免极端情况)
    double min_core = work[best_start];
    double max_core = work[best_stop];
    if (max_core <= min_core) {
      max_core *= 1.0 + 1e-10;
      min_core *= 1.0 - 1e-10;
    }

    // 步骤4:压缩尾部数据(单调变换,保留顺序)
    const double core_range = max_core - min_core;
    const double limit = core_range * (1.0 - cover);
    const double scale = -1.0 / core_range;

    for (int i = 0; i < n; ++i) {
      if (raw[i] < min_core) {
        // 左尾部压缩
        raw[i] = min_core - limit * (1.0 - exp(scale * (min_core - raw[i])));
      } else if (raw[i] > max_core) {
        // 右尾部压缩
        raw[i] = max_core + limit * (1.0 - exp(scale * (raw[i] - max_core)));
      }
      // 核心区间数据保持不变
    }

    return ERR_SUCCESS;
  }

  // 3. 指标非线性变换(提升熵值)
  // 参数:数据长度n、输入数据、变换类型(1:平方根 2:立方根 3:双曲正切
  // 4:逻辑函数)、输出数据
  static int ApplyNonlinearTransform(int n, const double* in_data,
                                     int transform_type, double* out_data) {
    if (in_data == nullptr || out_data == nullptr) return ERR_NULL_PTR;
    if (n <= 0) return ERR_EMPTY_DATA;

    // 预分配输出内存(若out_data未提前分配,此处需处理,此处假设已分配)
    for (int i = 0; i < n; ++i) {
      const double val = in_data[i];
      switch (transform_type) {
        case 1:  // 平方根(处理非负,适合右偏数据)
          out_data[i] = (val < 0.0) ? 0.0 : sqrt(val);
          break;
        case 2:  // 立方根(处理双尾极端值)
          out_data[i] = cbrt(val);
          break;
        case 3:  // 双曲正切(压缩极端值,需提前归一化)
          out_data[i] = tanh(val);
          break;
        case 4:  // 逻辑函数(压缩后居中到-0.5~0.5)
          out_data[i] = 1.0 / (1.0 + exp(-val)) - 0.5;
          break;
        default:
          out_data[i] = val;  // 默认无变换
          break;
      }
    }

    return ERR_SUCCESS;
  }
};

// 示例:交易系统中调用流程
int main() {
  // 假设从市场数据中读取的指标数据(如RawJump)
  const int data_len = 10000;
  double* raw_indicator = new double[data_len];
  double* work_buf = new double[data_len];               // 尾部清洗工作区
  double* transformed_indicator = new double[data_len];  // 变换后数据

  // 1. 计算原始指标的相对熵
  double raw_rel_entropy;
  int err = TradingIndicatorEntropy::CalculateRelativeEntropy(
      data_len, raw_indicator, 20, &raw_rel_entropy);
  if (err != ERR_SUCCESS) {
    // 实际系统中记录错误日志,此处简化
    goto cleanup;
  }

  // 2. 若熵值过低(如<0.5),先进行尾部清洗
  if (raw_rel_entropy < 0.5) {
    err = TradingIndicatorEntropy::CleanTails(data_len, raw_indicator, work_buf,
                                              0.05);  // 清洗5%尾部
    if (err != ERR_SUCCESS) goto cleanup;

    // 计算清洗后的熵值
    double cleaned_rel_entropy;
    err = TradingIndicatorEntropy::CalculateRelativeEntropy(
        data_len, raw_indicator, 20, &cleaned_rel_entropy);
    if (err != ERR_SUCCESS) goto cleanup;
  }

  // 3. 若熵值仍不足,应用非线性变换(如双曲正切)
  err = TradingIndicatorEntropy::ApplyNonlinearTransform(
      data_len, raw_indicator, 3, transformed_indicator);
  if (err != ERR_SUCCESS) goto cleanup;

  // 4. 计算最终优化后指标的相对熵(供交易模型使用)
  double final_rel_entropy;
  err = TradingIndicatorEntropy::CalculateRelativeEntropy(
      data_len, transformed_indicator, 20, &final_rel_entropy);

cleanup:
  // 释放内存(避免内存泄漏)
  delete[] raw_indicator;
  delete[] work_buf;
  delete[] transformed_indicator;
  return err;
}