本文基于外网文章翻译,并结合腾讯元宝根据A股市场实践整理,旨在分享量化投资从研究到实盘的实战心得。
关于策略
量化研究的核心并非寻找“回测曲线很漂亮”的策略,而是发掘那些有经济逻辑支撑、能被检验、可复现的Alpha。如果试图在此“偷懒”,后续所有工作都可能沦为一种“数据挖掘的自我感动”。
因此,我们构建的根基应是:一个可证伪的交易假设。
普通人的思路:
“我有个赚钱的好点子!我要去验证它是对的!”
量化研究员的思路:
“我有个可能赚钱的点子。但我先要给自己设置各种‘坑’,看看这个点子会不会掉进这些坑里。”
一个优秀的交易假设是策略的基石,必须拒绝笼统表述,满足具体性、可证伪性、经济合理性、有时限性四大核心要求。模糊的假设只会让后续研究迷失方向。
- 具体性:拒绝“动量策略有效”这类空泛表述,必须明确标的、周期、收益阈值。例如:“沪深300成分股中,过去12个月(剔除最后1个月)收益为正的资产,其未来1年收益率比收益为负的资产高出5%-10%”。
- 可证伪性:检验前提前定义明确的拒绝标准,避免“事后找理由”。例如:“若策略扣除交易成本后夏普比率小于0.5,或t统计量小于2.0,则直接拒绝该假设”。
- 金融逻辑合理性:回答核心问题“超额收益从何而来”,这是区分“真正Alpha”和“数据噪音”的关键。需从四大维度寻找逻辑支撑:行为偏差(投资者过度反应、损失厌恶、追涨杀跌)、结构性因素(指数再平衡、监管限制)、风险溢价(套利风险、流动性风险)、信息优势(信息扩散速度差异)。
- 有时限性:明确策略有效性的边界,以及优势消失的场景。例如:“该策略的超额收益来自散户对盈利意外的过度反应,若市场中算法交易占比提升、散户参与度下降,这一优势将逐步消失”。
在将任何因子或信号策略落地前,建议先思考以下4个问题,从根源上过滤无逻辑的策略:
- 这笔交易的另一方是谁? 若对手是消息灵通的机构,你很可能成为“接盘方”;若对手是资金受限的被动投资者(如指数基金),则超额收益更具真实性。
- 为什么这种套利行为尚未被消除? 是市场存在产能限制、实施成本过高,还是成熟投资者不愿承担的尾部风险?这是Alpha能持续的核心原因。
- 持仓周期和换手率对交易有何影响? 频率越高,执行风险(滑点、市场冲击)越大;周期越短,基本面风险(业绩变脸、政策调整)越大,需找到平衡点。
- 这种策略优势何时会停止? 需预判失效场景:市场风格切换、策略拥挤度上升、监管政策调整、市场结构发生根本性变化。
区分虚假信号与真正Alpha
在量化投资中,区分数据挖掘的虚假信号与真正的Alpha至关重要。虚假信号通常有八大特征:
- 策略没有合理的经济逻辑支撑,如同不知道为什么赢牌;
- 参数过度优化,像碰巧打开一个密码锁就宣称掌握了通用密码;
- 设计过度复杂,违背了“简单策略更稳健”的原则;
- 只通过样本内测试,如同学生只会做过的题目;
- 只在特定有利时期有效,无法穿越牛熊;
- 存在幸存者偏差,只统计存活样本;
- 依赖无法实现的精确择时;
- 假设不切实际的理想交易条件,忽略了真实的摩擦成本。
与之相对,真正的Alpha策略具备六大核心特征:
- 具备跨市场的普适性,如同优秀厨师精通多种菜系;
- 对参数扰动具有鲁棒性,不依赖精确的数字设定;
- 能适应不同的市场环境,在牛熊市中均能生存;
- 具有可扩展的容量,不会因规模扩大而失效;
- 拥有清晰且未被充分定价的经济逻辑,而非依赖数据巧合;
- 在充分考虑所有交易成本后仍能持续盈利。
简言之,真正的Alpha源于对市场规律的深刻理解,而非对历史数据的过度拟合。
一个“回测很好”的策略往往是在历史数据里“刻舟求剑”,但一上实盘就“见光死”。而一个真正能实盘赚钱的策略一定是逻辑清晰,简单稳健,经得起各种考验。因此在构建新策略前,可以先自问几个问题:
- “这策略为什么能赚钱?”
- “改几个参数还行吗?”
- “熊市那年能扛住吗?”
- “真有1个亿还能这样操作吗?”
- “扣掉所有费用还剩多少?”
同时,也要对自己的策略进行“淘汰更新”。当策略表现不佳时,首先要看是否触及“五个必须砍”的红线:
- 实盘跑一年还亏钱(夏普为负);
- 实际收益不到回测一半(前向效率<0.5);
- 买卖信号完全反了(相关性反转);
- 钱一多就赚不到差价(冲击成本吃光利润);
- 赚钱的逻辑基础已经不存在了(如市场规则变化)。
只要踩中任何一条,必须立即清仓止损。如果以上都没问题,但存在以下情况,则需要权衡是否放弃:
- 策略和现有持仓高度同质(无法分散风险);
- 运维成本远超收益(性价比过低);
- 存在极端尾部风险(平时小赚,危机时巨亏)。
记住,淘汰坏策略和发掘好策略同样重要。
关于数据
量化研究中,数据的质量直接决定策略的成败。前瞻性偏差、幸存者偏差、异常值处理不当,都会让回测结果成为“空中楼阁”。对于一些数据源有限的初创团队而言,掌握科学的数据管理方法尤为重要。
1. 异常值处理
金融数据(尤其是收益数据)天然具有厚尾分布,极端值不是“噪音”,而是真实的市场行为(如闪崩、盈利意外)。盲目剔除异常值会扭曲数据的真实性,正确的做法是“标记而非删除,调查而非忽略”。
def detect_and_handle_outliers(df, columns, method='iqr', threshold=3.0):
"""
检测并处理金融数据中的异常值,核心原则:标记不删除,保留真实市场特征
重要提示:切勿盲目删除金融数据中的异常值,极端值是真实市场行为的体现
"""
import numpy as np
import pandas as pd
df_clean = df.copy()
outlier_report = {} # 生成异常值报告,便于后续调查
for col in columns:
if col not in df.columns:
continue
series = df[col].dropna()
if method == 'iqr':
# 四分位距法,适合厚尾分布的金融数据
Q1 = series.quantile(0.25)
Q3 = series.quantile(0.75)
IQR = Q3 - Q1
lower = Q1 - threshold * IQR
upper = Q3 + threshold * IQR
outliers = (series < lower) | (series > upper)
else:
# z分数法,适用于近似正态分布的因子数据
mean = series.mean()
std = series.std()
z = np.abs((series - mean) / std)
outliers = z > threshold
# 标记异常值,不删除
df_clean[f'{col}_outlier'] = False
df_clean.loc[outliers.index[outliers], f'{col}_outlier'] = True
# 生成异常值报告,记录数量、占比、关键日期,便于后续事件调查
outlier_report[col] = {
'count': outliers.sum(),
'pct': outliers.mean() * 100,
'dates': series.index[outliers].tolist()[:10]
}
# 异常值后续处理:结合市场事件调查(如闪崩、财报发布、政策调整)
return df_clean, outlier_report
2. 缺失数据处理
数据缺失往往暗藏市场信号(如公司停牌、业绩披露延迟)。处理的核心是拒绝会引入前瞻偏差的方法,同时重视缺失数据背后的信息。
核心规则:
- 切勿使用前向填充(bfill)——这是引入前瞻性偏差的重灾区;
- 反向填充(ffill)仅适用于参考数据(如行业分类),绝不适用于价格、收益率等核心交易数据;
- 对金融数据进行插值(线性、多项式)极其危险。
可接受的方法:
- 直接删除缺少关键数据的行;
- 参考数据使用最后一个已知值(
shift(1).ffill()),且需明确标注;
- 将缺失值标记为NaN,在策略逻辑中单独处理;
- 利用多个数据源交叉验证。
3. 股票池处理(幸存者偏差)
仅对当前存续的证券进行测试,会忽略已退市或被剔除的标的,这些标的往往具有低收益、高风险特征,导致策略年收益率被虚高1-2%。预防措施包括:
- 使用时点成分列表,还原研究区间内的真实标的池;
- 将退市、被剔除的证券纳入测试;
- 优先使用无幸存者偏差的专业数据库;
- 对免费数据源保持怀疑,手动补充缺失数据。
4. 时间点数据管理
上市公司财报常被重述,若使用重述后的数据进行回测(如用5月修订的数据做4月的交易决策),就构成了严重的“用未来数据决策”。解决方案包括:使用时间点数据库获取原始版本数据、以实际公布日期作为纳入策略的时点、并对基本面数据施加保守的滞后(如财报后45天再使用)。
5. 特征归一化
必须杜绝前瞻性偏差。若使用整个样本期的统计量对因子进行标准化,就等于让策略在回测中“预知”了未来的整体分布。正确做法是严格使用滚动窗口或扩展窗口进行归一化,仅基于历史数据计算统计量。
def proper_feature_normalization(df, feature_cols, method='zscore', window=252):
"""
滚动归一化,从根源防止前瞻性偏差
关键原则:始终使用扩展窗口或滚动窗口,切勿使用整个样本进行归一化
"""
import numpy as np
import pandas as pd
df_norm = df.copy()
for col in feature_cols:
if col not in df.columns:
continue
series = df[col]
if method == 'zscore':
# 滚动z分数归一化,消除量纲影响
rolling_mean = series.rolling(window, min_periods=window//2).mean()
rolling_std = series.rolling(window, min_periods=window//2).std()
df_norm[f'{col}_norm'] = (series - rolling_mean) / (rolling_std + 1e-8) # 加小值避免除零
elif method == 'rank':
# 滚动秩归一化,适用于非正态分布的因子
df_norm[f'{col}_norm'] = series.rolling(window).apply(
lambda x: (x.iloc[-1] > x[:-1]).mean() if len(x) > 1 else 0.5,
raw=False
)
elif method == 'minmax':
# 滚动最大最小值归一化,将因子映射到[0,1]区间
rolling_min = series.rolling(window).min()
rolling_max = series.rolling(window).max()
df_norm[f'{col}_norm'] = (series - rolling_min) / (rolling_max - rolling_min + 1e-8)
return df_norm
6. 数据平稳性
绝大多数金融时间序列(如价格、交易量)都具有非平稳特性,直接建模易导致虚假回归。建模前必须通过变换转换为平稳序列,常用变换包括:收益率变换、差分变换、比率变换、Z分数变换。铁律:对于任何机器学习模型,原始价格绝不能直接作为输入,必须转换为收益率或其他平稳序列。
7. 回溯窗口选择
回溯窗口的长度选择,本质是在策略响应速度与信号稳健性之间寻求平衡:
- 波动周期策略:20-60天窗口(月度至季度);
- 趋势与动量策略:60-252天窗口(季度至年度);
- 均值回归策略:5-20天窗口(每周到每月);
- 相关性计算:60-126天窗口。
一个实用原则是:根据市场波动状态灵活调整——高波动时期缩短窗口,低波动时期延长窗口。
关于回测
回测的核心目的不是“证明策略有效”,而是尽可能还原实盘场景,发现策略的潜在问题。回测的严格性直接决定策略实盘的存活率。
1. 前瞻性偏差
前瞻性偏差是指使用未来信息做出本应仅基于过去信息的决策,是导致“回测暴利、实盘亏损”的最常见且隐蔽的原因。其主要来源包括六大方面,前文在数据部分已提及。此外,可通过统计方法检测:
def check_lookahead_bias(df, signal_col, return_col, horizon=1):
"""
前瞻偏差的统计检验,核心逻辑:存在前瞻偏差的信号,与过去收益相关性异常高
"""
import numpy as np
from scipy.stats import spearmanr
signal = df[signal_col].dropna()
results = {}
# 检测信号与过去收益的相关性(正常情况下应极低)
for lag in [1, 2, 5, 10]:
past_ret = df[return_col].shift(lag)
valid = signal.notna() & past_ret.notna()
if valid.sum() > 30: # 保证足够的观测数据
corr, pval = spearmanr(signal[valid], past_ret[valid])
results[f'corr_lag_{lag}'] = {'corr': corr, 'pval': pval}
# 预警:相关性绝对值>0.1且p值<0.05,大概率存在前瞻偏差
if abs(corr) > 0.1 and pval < 0.05:
print(f"警告:信号与{lag}天的过去收益存在显著相关性!")
print(f"相关性:{corr:.4f},p值:{pval:.4f}")
# 检测信号与未来收益的相关性(策略有效时应显著为正/负)
fwd_ret = df[return_col].shift(-horizon)
valid = signal.notna() & fwd_ret.notna()
if valid.sum() > 30:
corr, pval = spearmanr(signal[valid], fwd_ret[valid])
results['corr_forward'] = {'corr': corr, 'pval': pval}
return results
2. 过拟合
过拟合是指策略过度拟合历史数据中的噪声,而非捕捉真实的市场规律。识别过拟合有六大核心信号:样本内外夏普比率差距悬殊;前向行走效率低于0.5;参数微调导致收益断崖下跌;随着测试次数增加策略越发复杂;完美契合历史噪声却在模拟数据中表现差;可复现性差。缓解措施包括:采用正则化技术、使用严格的时间序列交叉验证、限制模型复杂度、优先选用简单清晰的信号。
3. 交易成本模型
回测中常见的“零成本假设”会严重高估策略表现。一个贴合实际的交易成本模型应包含佣金、买卖价差、市场冲击成本、滑点乃至空头借贷费用。
class TransactionCostModel:
"""
用于回测的实际交易成本模型,全面考虑佣金、点差、市场冲击、滑点、借贷成本
适配大多数对冲基金的实盘交易场景,参数可根据券商费率、市场情况调整
"""
def __init__(self,
commission_pct=0.0005, # 佣金比例,默认万5
spread_bps=5, # 买卖价差成本,单位:基点,默认5个基点
market_impact_bps=2, # 市场冲击成本,每100万美元交易的基点数
borrow_cost_annual=0.005, # 空头头寸的年化借贷成本,默认50个基点
min_commission=1.0): # 最低佣金,默认1元
self.commission_pct = commission_pct
self.spread_bps = spread_bps / 10000 # 转换为比例
self.market_impact_bps = market_impact_bps / 10000
self.borrow_cost_annual = borrow_cost_annual
self.min_commission = min_commission
def compute_cost(self, trade_value, is_short=False, adv=None):
"""
计算单笔交易的总成本
参数:
-----------
trade_value : float
交易的绝对美元/人民币价值
is_short : bool
是否为空头头寸,空头需计算借贷成本
adv : float, optional
标的平均每日交易量(美元/人民币),用于计算市场冲击
返回值:
--------
total_cost : float
总交易成本(美元/人民币)
cost_breakdown : dict
交易成本按组成部分分解,便于分析各成本占比
"""
import numpy as np
# 佣金成本,取比例佣金和最低佣金的最大值
commission = max(trade_value * self.commission_pct, self.min_commission)
# 买卖价差成本
spread_cost = trade_value * self.spread_bps
# 市场冲击成本,遵循平方根定律(交易规模越大,冲击成本越高)
if adv and adv > 0:
participation = trade_value / adv # 交易规模占日均交易量的比例
market_impact = trade_value * self.market_impact_bps * np.sqrt(participation)
else:
market_impact = trade_value * self.market_impact_bps
# 空头借贷成本,按20个交易日(月均)计算
borrow_cost = 0
if is_short:
borrow_cost = trade_value * self.borrow_cost_annual * (20 / 252)
# 总交易成本
total_cost = commission + spread_cost + market_impact + borrow_cost
# 成本分解
cost_breakdown = {
'commission': commission,
'spread': spread_cost,
'market_impact': market_impact,
'borrow_cost': borrow_cost
}
return total_cost, cost_breakdown
4. 前向分析
前向分析(Walk Forward Analysis)是最贴近实盘的验证方法,核心逻辑是“用历史数据逐步训练模型,用后续数据逐步测试模型”,能有效检测策略的样本外稳健性。
def walk_forward_analysis(df, feature_cols, target_col,
min_train_days=252,
refit_frequency=20,
embargo_days=10,
model_class=None):
"""
具有适当时间结构的前向分析,时间序列验证的黄金标准
核心逻辑:训练集→禁运期→测试集,避免信息泄露,还原实盘迭代场景
"""
import numpy as np
import pandas as pd
from sklearn.preprocessing import StandardScaler
results = []
fold_stats = []
# 过滤缺失值,保证数据有效性
valid_mask = df[feature_cols + [target_col]].notna().all(axis=1)
valid_data = df[valid_mask].copy()
train_end_idx = min_train_days # 最小训练天数,保证模型有足够的训练数据
fold_num = 0
while train_end_idx < len(valid_data) - embargo_days:
fold_num += 1
# 划分训练集:起始到train_end - 禁运期,避免信息泄露
train_data = valid_data.iloc[:train_end_idx - embargo_days]
# 划分测试集:train_end到train_end + 重拟合频率
test_start = train_end_idx
test_end = min(test_start + refit_frequency, len(valid_data))
test_data = valid_data.iloc[test_start:test_end]
if len(test_data) == 0:
break
# 提取特征和标签
X_train = train_data[feature_cols].values
y_train = train_data[target_col].values
X_test = test_data[feature_cols].values
y_test = test_data[target_col].values
# 特征标准化:仅用训练集数据拟合,避免前瞻偏差
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
# 模型训练与预测
if model_class:
model = model_class()
model.fit(X_train_scaled, y_train)
predictions = model.predict(X_test_scaled)
else:
predictions = np.zeros(len(y_test)) # 无模型时返回空预测
# 记录每一次预测的结果
for i, (idx, pred, actual) in enumerate(zip(test_data.index, predictions, y_test)):
results.append({
'date': idx,
'prediction': pred,
'actual': actual,
'fold': fold_num
})
# 记录每一轮的折数统计,便于后续分析
fold_stats.append({
'fold': fold_num,
'train_start': train_data.index[0],
'train_end': train_data.index[-1],
'test_start': test_data.index[0],
'test_end': test_data.index[-1],
'train_samples': len(train_data),
'test_samples': len(test_data)
})
# 移动训练集终点,进行下一轮前向分析
train_end_idx += refit_frequency
return pd.DataFrame(results), fold_stats
5. 时间序列中的未来数据泄露
当预测目标为多期收益时,标准的随机划分会导致标签期的重叠而产生信息泄露。解决方案是引入 “清除(Purge)” 和 “禁运(Embargo)” 技术。“清除”是在训练集末端移除一定数量的样本(通常为标签跨越周期H-1天)。“禁运”是在清除之后,于训练集与测试集之间再设置一段与标签周期H相当的空白间隔期。
6. 综合绩效评价
单一的夏普比率无法全面评估策略,实盘需要关注回撤、风险调整后收益、极端风险等。因此回测中需要计算综合绩效指标。
def compute_robust_metrics(returns, benchmark_returns=None, rf_rate=0.0):
"""
计算量化策略的综合性能指标,全面评估收益、风险、回撤、统计显著性等
"""
import numpy as np
import pandas as pd
from scipy.stats import skew, kurtosis, t
r = returns.dropna()
n = len(r)
if n < 30: # 保证足够的观测数据,避免指标失真
return {'error': '数据不足(需要30个以上观测值)'}
# 基础收益指标
annual_return = r.mean() * 252 # 年化收益,按252个交易日计算
annual_vol = r.std() * np.sqrt(252) # 年化波动率
sharpe = annual_return / annual_vol if annual_vol > 0 else 0 # 夏普比率
# 回撤相关指标(实盘最关注的风险指标)
cum_returns = (1 + r).cumprod() # 累计收益
running_max = cum_returns.expanding().max() # 滚动最大值
drawdowns = (cum_returns - running_max) / running_max # 回撤序列
max_dd = drawdowns.min() # 最大回撤
calmar = annual_return / abs(max_dd) if max_dd != 0 else np.nan # 卡玛比率
# 高阶矩:偏度和峰度,评估收益分布的极端风险
ret_skew = skew(r) # 偏度:<0为左偏,存在极端下跌风险;>0为右偏
ret_kurt = kurtosis(r) # 峰度:>0为厚尾,极端行情发生概率更高
# 下行风险指标:索提诺比率(仅考虑下行波动率)
downside_returns = r[r < 0]
downside_vol = downside_returns.std() * np.sqrt(252) if len(downside_returns) > 0 else annual_vol
sortino = annual_return / downside_vol if downside_vol > 0 else 0
# 统计显著性:t统计量和p值,评估收益的统计可靠性
t_stat = sharpe * np.sqrt(n / 252)
p_value = 2 * (1 - t.cdf(abs(t_stat), df=n-1))
# 盈亏比指标:欧米茄比率
gains = r[r > 0].sum() # 总盈利
loss = abs(r[r < 0].sum()) # 总亏损
omega = gains / loss if loss > 0 else np.nan
# 基础绩效指标字典
metrics = {
'annual_return': annual_return,
'cumulative_return': cum_returns.iloc[-1] - 1,
'annual_volatility': annual_vol,
'max_drawdown': max_dd,
'avg_drawdown': drawdowns.mean(),
'sharpe_ratio': sharpe,
'sortino_ratio': sortino,
'calmar_ratio': calmar,
'omega_ratio': omega,
't_statistic': t_stat,
'p_value': p_value,
'significant_5pct': p_value < 0.05,
'skewness': ret_skew,
'kurtosis': ret_kurt,
'n_observations': n,
'hit_rate': (r > 0).mean(),
'profit_factor': gains / loss if loss > 0 else np.nan
}
# 相对基准的绩效指标(如超额收益、阿尔法、贝塔)
if benchmark_returns is not None:
bm = benchmark_returns.reindex(r.index).dropna()
if len(bm) > 30:
excess = r.loc[bm.index] - bm # 超额收益
tracking_error = excess.std() * np.sqrt(252) # 跟踪误差
info_ratio = excess.mean() * 252 / tracking_error if tracking_error > 0 else 0 # 信息比率
# 计算阿尔法和贝塔
cov = r.loc[bm.index].cov(bm)
bm_var = bm.var()
beta = cov / bm_var if bm_var > 0 else 1
alpha = (r.loc[bm.index].mean() - rf_rate / 252 - beta * (bm.mean() - rf_rate / 252)) * 252
# 更新相对指标
metrics.update({
'alpha': alpha,
'beta': beta,
'information_ratio': info_ratio,
'tracking_error': tracking_error,
'annual_excess_return': excess.mean() * 252
})
return metrics
7. 参数敏感性分析
稳健的量化策略不应因参数的微小扰动而急剧退化。参数敏感性分析的核心是测试策略在参数合理范围内的表现,筛选出参数稳健的策略。
def parameter_sensitivity_analysis(strategy_func, base_params, param_ranges, df):
"""
分析策略性能对参数变化的敏感度,评估策略的稳健性
核心逻辑:在参数合理范围内微调,观察策略夏普比率的变化
"""
import numpy as np
results = {}
sensitivity_metrics = {}
# 遍历每个待测试的参数
for param_name, values in param_ranges.items():
results[param_name] = []
# 遍历参数的每个取值
for val in values:
test_params = base_params.copy()
test_params[param_name] = val # 微调当前参数
try:
# 运行策略,获取核心绩效指标(夏普比率)
sharpe = strategy_func(df, **test_params)
results[param_name].append({'value': val, 'sharpe': sharpe})
except Exception as e:
# 捕获策略运行错误,标记为NaN
results[param_name].append({'value': val, 'sharpe': np.nan, 'error': str(e)})
# 计算参数敏感性指标,评估策略对该参数的敏感程度
sharpes = [r['sharpe'] for r in results[param_name] if not np.isnan(r['sharpe'])]
if len(sharpes) > 1:
sensitivity_metrics[param_name] = {
'mean_sharpe': np.mean(sharpes),
'std_sharpe': np.std(sharpes),
'cv': np.std(sharpes) / abs(np.mean(sharpes)) if np.mean(sharpes) != 0 else np.nan, # 变异系数
'min_sharpe': np.min(sharpes),
'max_sharpe': np.max(sharpes)
}
return results, sensitivity_metrics
关于实盘
在初创对冲基金,研究出有效策略只是第一步,将策略平稳落地到实盘,实现持续收益输出,才是核心价值。这一步需要解决研究代码与生产代码的分离、仓位管理、风险控制、Alpha衰变检测等问题。
研究代码和生产代码的核心目标不同,必须严格分离。
- 研究代码:核心是快速迭代、验证假设,允许探索性混乱,可使用Jupyter Notebook,注重速度。
- 生产代码:核心是稳健、可靠、可维护,必须是清洁、有注释、经过严格测试的Python模块,采用配置驱动,注重稳定性。
从研究到生产的建议流程:
- 冻结研究代码版本,固定策略核心逻辑和参数。
- 提取核心逻辑,封装为独立Python模块,去除探索性代码。
- 添加综合测试(单元测试、集成测试)。
- 采用配置驱动,将所有可调参数放入配置文件。
- 进行代码审查。
- 先部署到模拟交易中运行验证,再投入实盘。
仓位管理
仓位规模调整的核心是在策略收益和交易风险之间找平衡。初创基金资金规模有限,更需要科学的仓位调整方法。
class PositionSizer:
"""
生产就绪的仓位规模调整方法,支持凯利准则、波动率目标、风险平价三种核心方法
适配对冲基金实盘交易场景,可根据策略类型和资金规模灵活选择
"""
@staticmethod
def kelly_criterion(win_rate, avg_win, avg_loss, fraction=0.25):
"""
分数凯利准则仓位调整,适用于趋势类、择时类策略
核心:完全凯利准则最优但波动率过大,生产中使用1/4-1/2分数凯利,平衡收益和风险
参数:
win_rate: 策略胜率
avg_win: 平均盈利
avg_loss: 平均亏损
fraction: 分数凯利系数,默认0.25(最稳健)
"""
if avg_loss == 0:
return 0
b = avg_win / avg_loss # 盈亏比
p = win_rate
q = 1 - win_rate
kelly = (p * b - q) / b # 标准凯利公式
return max(0, min(kelly * fraction, 1.0)) # 仓位限制在0-1之间
@staticmethod
def volatility_targeting(returns, target_vol, current_position=1.0,
lookback=20, vol_cap=2.0):
"""
波动率目标仓位调整,适用于多因子、指数增强策略
核心:调整仓位规模,使策略的实际波动率始终贴近目标波动率
参数:
returns: 策略历史收益序列
target_vol: 目标年化波动率
current_position: 当前仓位
lookback: 回溯窗口,默认20天
vol_cap: 仓位缩放上限,默认2.0(避免仓位过高)
"""
import numpy as np
recent_returns = returns.iloc[-lookback:]
implemented_vol = recent_returns.std() * np.sqrt(252) # 实际年化波动率
if implemented_vol <= 0:
return current_position
scale = target_vol / implemented_vol # 仓位缩放比例
scale = np.clip(scale, 1/vol_cap, vol_cap) # 限制缩放比例
return current_position * scale
@staticmethod
def risk_parity_weights(covariance_matrix, target_risk=None):
"""
风险平价仓位分配,适用于多资产、多策略组合
核心:让每个资产/策略对组合的风险贡献相等,实现风险分散
参数:
covariance_matrix: 资产/策略的协方差矩阵
target_risk: 组合的目标年化波动率
"""
import numpy as np
import pandas as pd
from scipy.optimize import minimize
cov = covariance_matrix.values
n = len(cov)
# 风险平价目标函数:最小化各资产风险贡献的方差
def risk_budget_objective(weights):
port_vol = np.sqrt(weights @ cov @ weights)
marginal_contrib = cov @ weights / port_vol
risk_contrib = weights * marginal_contrib
return np.sum((risk_contrib - port_vol/n)**2)
# 约束条件:权重和为1,单权重在0-1之间(不做空)
constraints = [{'type': 'eq', 'fun': lambda w: np.sum(w) - 1}]
bounds = [(0, 1) for _ in range(n)]
# 优化求解风险平价权重
result = minimize(
risk_budget_objective,
x0=np.ones(n)/n, # 初始权重:等权重
method='SLSQP',
bounds=bounds,
constraints=constraints
)
weights = pd.Series(result.x, index=covariance_matrix.columns)
# 若设置目标风险,调整权重使组合风险贴近目标风险
if target_risk:
current_risk = np.sqrt(weights.values @ cov @ weights.values) * np.sqrt(252)
weights = weights * (target_risk / current_risk)
return weights
Alpha 衰变探测
量化策略的Alpha不是永恒的。实盘中必须建立Alpha衰变探测机制,实时监控策略表现。
核心探测方法:
- 滚动绩效指标监控:实时计算滚动夏普比率等,与回测指标对比,若持续低于历史水平的50%,则预警。
- 收益分布监控:监控实盘收益的偏度、峰度、胜率,若发生根本性变化(如胜率大幅下降),则说明核心逻辑可能失效。
- 因子有效性监控:对于多因子策略,实时监控每个因子的IC值、IC_IR,若核心因子有效性持续下降,需及时替换。
- 策略拥挤度监控:通过市场成交量、资金流向等指标监控策略拥挤度,若持续上升,则Alpha可持续性下降。
Alpha衰变的应对策略:
- 短期:降低仓位,减少交易频率。
- 中期:调整参数、替换失效因子、优化策略逻辑。
- 长期:若核心经济逻辑已失效,果断停止策略,转移资源。
总结
在初创对冲基金做量化研究,最大的收获是学会了用“实盘思维”做研究——懂得经济逻辑比回测收益更重要,稳健性比高收益更重要,风险控制比盈利更重要。
量化研究的本质,是在市场的非有效性中寻找可复现的Alpha,并通过严格的风险控制将其转化为实盘收益。初创基金的优势是“船小好调头”,劣势是资源有限、抗风险能力弱。因此,必须坚持“简单、稳健、可落地”的原则,守住“研究有逻辑、回测贴实盘、实盘能盈利、风险可控制”的核心底线。
作为量化研究员,最核心的能力不是编写复杂的代码,而是独立的思考能力、严谨的研究方法、敏锐的市场洞察力,以及果断的止损意识。懂得如何提出有价值的假设,如何严谨验证,如何发现问题,如何在实盘中控制风险,如何在Alpha衰变时及时止损——这些能力,才是最宝贵的财富。
欢迎在云栈社区继续交流探讨量化投资与人工智能在智能&数据&云领域的应用实践。