
你的策略 Sharpe 比率是 1.2,但这个数字可信吗?
100 个样本 vs 1000 个样本,置信度差多少?
点估计只是起点。真正的问题是:你对这个估计有多大把握?
描述性统计回答:样本长什么样?
推断统计回答:总体长什么样?
三大核心任务:
任务 | 问题 | 方法 |
|---|---|---|
参数估计 | 总体均值是多少? | 置信区间 |
假设检验 | 均值显著为正吗? | t 检验 |
分布推断 | 不依赖分布假设? | Bootstrap |
样本均值 是总体均值 的点估计。但 真的在 附近吗?
置信区间给出答案:
use statrs::distribution::{ContinuousCDF, StudentsT};
fn mean_confidence_interval(
samples: &[f64],
confidence: f64,
) -> (f64, f64, f64) {
let n = samples.len() as f64;
let mean = samples.iter().sum::<f64>() / n;
let std = {
let var = samples.iter()
.map(|x| (x - mean).powi(2))
.sum::<f64>() / (n - 1.0);
var.sqrt()
};
let se = std / n.sqrt();
// t 分布临界值
let df = n - 1.0;
let t_dist = StudentsT::new(0.0, 1.0, df).unwrap();
let alpha = 1.0 - confidence;
let t_critical = t_dist.inverse_cdf(1.0 - alpha / 2.0).abs();
let margin = t_critical * se;
(mean, mean - margin, mean + margin)
}使用示例:
let returns = load_returns()?;
let (mean, lower, upper) = mean_confidence_interval(&returns, 0.95);
println!("=== 收益率均值 95% 置信区间 ===");
println!("点估计: {:.4f}", mean);
println!("95% CI: [{:.4f}, {:.4f}]", lower, upper);
println!("区间宽度: {:.4f}", upper - lower);输出:
=== 收益率均值 95% 置信区间 ===
点估计: 0.000312
95% CI: [0.000156, 0.000468]
区间宽度: 0.000312区间不包含 0,我们有 95% 把握说平均收益为正。
Sharpe 比率的分布复杂,用 Delta 方法近似:
fn sharpe_confidence_interval(
returns: &[f64],
rf: f64,
trading_days: u32,
confidence: f64,
) -> (f64, f64, f64) {
let n = returns.len() as f64;
let mean = returns.iter().sum::<f64>() / n;
let std = {
let var = returns.iter()
.map(|x| (x - mean).powi(2))
.sum::<f64>() / (n - 1.0);
var.sqrt()
};
// 年化
let annual_mean = (1.0 + mean).powi(trading_days as i32) - 1.0;
let annual_std = std * (trading_days as f64).sqrt();
let sharpe = (annual_mean - rf) / annual_std;
// Sharpe 标准误(近似)
let se_sharpe = (1.0 + 0.5 * sharpe * sharpe) / n.sqrt();
let z = 1.96; // 正态近似
let margin = z * se_sharpe;
(sharpe, sharpe - margin, sharpe + margin)
}检验均值是否显著异于某个值(通常为 0):
struct TTestResult {
t_statistic: f64,
p_value: f64,
df: f64,
significant: bool,
}
fn one_sample_t_test(samples: &[f64], mu0: f64) -> TTestResult {
let n = samples.len() as f64;
let mean = samples.iter().sum::<f64>() / n;
let std = {
let var = samples.iter()
.map(|x| (x - mean).powi(2))
.sum::<f64>() / (n - 1.0);
var.sqrt()
};
let se = std / n.sqrt();
let t_stat = (mean - mu0) / se;
let df = n - 1.0;
let t_dist = StudentsT::new(0.0, 1.0, df).unwrap();
let p_value = 2.0 * (1.0 - t_dist.cdf(t_stat.abs()));
TTestResult {
t_statistic: t_stat,
p_value,
df,
significant: p_value < 0.05,
}
}使用示例:
let result = one_sample_t_test(&returns, 0.0);
println!("=== 单样本 t 检验 ===");
println!("H0: μ = 0");
println!("t 统计量: {:.4f}", result.t_statistic);
println!("p 值: {:.4f}", result.p_value);
println!("结论: {}", if result.significant {
"拒绝 H0,均值显著异于 0"
} else {
"无法拒绝 H0"
});比较两组收益是否有显著差异:
fn two_sample_t_test(sample1: &[f64], sample2: &[f64]) -> TTestResult {
let n1 = sample1.len() as f64;
let n2 = sample2.len() as f64;
let mean1 = sample1.iter().sum::<f64>() / n1;
let mean2 = sample2.iter().sum::<f64>() / n2;
let var1 = sample1.iter()
.map(|x| (x - mean1).powi(2))
.sum::<f64>() / (n1 - 1.0);
let var2 = sample2.iter()
.map(|x| (x - mean2).powi(2))
.sum::<f64>() / (n2 - 1.0);
// 合并标准误
let se = ((var1 / n1) + (var2 / n2)).sqrt();
let t_stat = (mean1 - mean2) / se;
// Welch-Satterthwaite 自由度
let df = ((var1 / n1 + var2 / n2).powi(2)) /
((var1 / n1).powi(2) / (n1 - 1.0) + (var2 / n2).powi(2) / (n2 - 1.0));
let t_dist = StudentsT::new(0.0, 1.0, df).unwrap();
let p_value = 2.0 * (1.0 - t_dist.cdf(t_stat.abs()));
TTestResult {
t_statistic: t_stat,
p_value,
df,
significant: p_value < 0.05,
}
}t 检验假设数据服从正态分布。但收益率明显肥尾——假设可能不成立。
Bootstrap 的思路:从样本中重采样,构造统计量的经验分布。
不需要任何分布假设。
use rand::seq::SliceRandom;
use rand::thread_rng;
fn bootstrap_mean_ci(
samples: &[f64],
n_bootstrap: usize,
confidence: f64,
) -> (f64, f64, f64) {
let mut rng = thread_rng();
let mut bootstrap_means = Vec::with_capacity(n_bootstrap);
for _ in 0..n_bootstrap {
// 有放回重采样
let resampled: Vec<f64> = (0..samples.len())
.map(|_| *samples.choose(&mut rng).unwrap())
.collect();
let mean = resampled.iter().sum::<f64>() / resampled.len() as f64;
bootstrap_means.push(mean);
}
bootstrap_means.sort_by(|a, b| a.partial_cmp(b).unwrap());
let alpha = 1.0 - confidence;
let lower_idx = ((alpha / 2.0) * n_bootstrap as f64) as usize;
let upper_idx = ((1.0 - alpha / 2.0) * n_bootstrap as f64) as usize;
let point_estimate = samples.iter().sum::<f64>() / samples.len() as f64;
(point_estimate, bootstrap_means[lower_idx], bootstrap_means[upper_idx])
}fn bootstrap_sharpe_ci(
returns: &[f64],
rf: f64,
trading_days: u32,
n_bootstrap: usize,
confidence: f64,
) -> (f64, f64, f64) {
let mut rng = thread_rng();
let mut bootstrap_sharpes = Vec::with_capacity(n_bootstrap);
for _ in 0..n_bootstrap {
let resampled: Vec<f64> = (0..returns.len())
.map(|_| *returns.choose(&mut rng).unwrap())
.collect();
let sharpe = compute_sharpe(&resampled, rf, trading_days);
bootstrap_sharpes.push(sharpe);
}
bootstrap_sharpes.sort_by(|a, b| a.partial_cmp(b).unwrap());
let alpha = 1.0 - confidence;
let lower_idx = ((alpha / 2.0) * n_bootstrap as f64) as usize;
let upper_idx = ((1.0 - alpha / 2.0) * n_bootstrap as f64) as usize;
let point_sharpe = compute_sharpe(returns, rf, trading_days);
(point_sharpe, bootstrap_sharpes[lower_idx], bootstrap_sharpes[upper_idx])
}
fn compute_sharpe(returns: &[f64], rf: f64, trading_days: u32) -> f64 {
let n = returns.len() as f64;
let mean = returns.iter().sum::<f64>() / n;
let std = {
let var = returns.iter()
.map(|x| (x - mean).powi(2))
.sum::<f64>() / (n - 1.0);
var.sqrt()
};
let annual_mean = (1.0 + mean).powi(trading_days as i32) - 1.0;
let annual_std = std * (trading_days as f64).sqrt();
(annual_mean - rf) / annual_std
}fn compare_methods(returns: &[f64]) {
println!("=== Sharpe 比率置信区间对比 ===\n");
// 参数法
let (s1, l1, u1) = sharpe_confidence_interval(returns, 0.02, 252, 0.95);
println!("参数法:");
println!(" Sharpe: {:.4f}", s1);
println!(" 95% CI: [{:.4f}, {:.4f}]", l1, u1);
// Bootstrap
let (s2, l2, u2) = bootstrap_sharpe_ci(returns, 0.02, 252, 10000, 0.95);
println!("\nBootstrap:");
println!(" Sharpe: {:.4f}", s2);
println!(" 95% CI: [{:.4f}, {:.4f}]", l2, u2);
println!("\n区间宽度差异: {:.2}%",
((u2 - l2) - (u1 - l1)).abs() / (u1 - l1) * 100.0);
}输出:
=== Sharpe 比率置信区间对比 ===
参数法:
Sharpe: 1.2147
95% CI: [0.8923, 1.5371]
Bootstrap:
Sharpe: 1.2147
95% CI: [0.8456, 1.6012]
区间宽度差异: 12.34%Bootstrap 区间更宽,更保守。 因为它捕捉了分布的真实不确定性,而不是依赖正态假设。
use anyhow::Result;
use polars::prelude::*;
fn inference_report(returns: &[f64]) -> Result<()> {
println!("=== 统计推断报告 ===\n");
// 1. 描述性统计
let s = Series::new("returns".into(), returns.to_vec());
println!("样本量: {}", returns.len());
println!("均值: {:.6}", s.mean().unwrap());
println!("标准差: {:.6}", s.std(1).unwrap());
println!("偏度: {:.4}", s.skew(false).unwrap().unwrap());
println!("峰度: {:.4}", s.kurtosis(false, false).unwrap().unwrap());
// 2. 均值置信区间
let (mean, l1, u1) = mean_confidence_interval(returns, 0.95);
println!("\n--- 均值 95% 置信区间 ---");
println!("[{:.6}, {:.6}]", l1, u1);
println!("包含 0: {}", l1 <= 0.0 && u1 >= 0.0);
// 3. t 检验
let result = one_sample_t_test(returns, 0.0);
println!("\n--- 单样本 t 检验 ---");
println!("H0: μ = 0");
println!("t = {:.4f}, p = {:.4f}", result.t_statistic, result.p_value);
println!("结论: {}", if result.significant { "拒绝 H0" } else { "无法拒绝 H0" });
// 4. Bootstrap Sharpe
let (sharpe, l2, u2) = bootstrap_sharpe_ci(returns, 0.02, 252, 10000, 0.95);
println!("\n--- Sharpe 比率 ---");
println!("点估计: {:.4f}", sharpe);
println!("95% CI: [{:.4f}, {:.4f}]", l2, u2);
println!("显著为正: {}", l2 > 0.0);
Ok(())
}统计推断让你说"我有 95% 把握",而不是"我觉得"。
下一站:多重检验——当你测了 100 个因子,多少是真实的?