一、参数优化的核心战场:过拟合与欠拟合
做量化交易的朋友,尤其是搞暗池策略的,一定绕不开这两个词:过拟合和欠拟合。说白了,它们就是参数优化的「阴阳两面」。
1.1 欠拟合:你的策略太「懒」了
欠拟合,就是模型太简单,连历史数据里的规律都没抓住。我见过不少新手,拿一个简单的移动平均线策略去跑暗池数据,结果回测曲线跟心电图似的——毫无规律。为什么?因为暗池里的订单流特征,根本不是几条均线能描述的。
典型症状:
- 回测收益低,夏普比率小于0.5
- 训练集和测试集表现都很差
- 参数稍微一动,结果就崩
1.2 过拟合:你的策略太「聪明」了
过拟合恰恰相反。模型把历史数据里的噪声都当成了规律,学得「太像」了。我在2018年做过一个暗池策略,回测年化收益80%,最大回撤只有3%。当时我兴奋得不行,结果实盘第一周就亏了12%。
为什么会这样?因为我把某个特定时间段的市场微观结构噪声,当成了永恒规律。
典型症状:
- 训练集收益极高,测试集收益极低
- 参数数量远多于有效样本量
- 策略对参数取值极其敏感
二、回测框架搭建:你的实验台要稳
回测框架,就是你的「实验台」。台子不稳,实验结果全是废的。我个人习惯用Python搭建,核心组件就这几块:
2.1 数据层
暗池数据跟普通交易所数据不一样。你需要处理的是Level 3级别的订单簿数据,包括隐藏订单、冰山订单、以及各种暗池特有的订单类型。
class DarkPoolDataFeed:
def __init__(self, data_path):
self.data = self._load_parquet(data_path)
self.current_idx = 0
def next_tick(self):
# 返回当前时间片的订单簿快照
tick = self.data.iloc[self.current_idx]
self.current_idx += 1
return tick
2.2 策略引擎
策略引擎负责执行你的交易逻辑。这里要注意:暗池交易通常有最小成交量限制和价格保护机制,回测时必须模拟这些约束。
class DarkPoolStrategy:
def __init__(self, params):
self.min_volume = params['min_volume']
self.price_tolerance = params['price_tolerance']
def on_tick(self, tick):
# 你的策略逻辑写在这里
signal = self._generate_signal(tick)
if signal > 0.7:
return self._place_order(tick, 'buy')
return None
2.3 风控模块
这个模块很多人会忽略。我建议至少包含:最大持仓限制、单笔亏损上限、日内交易次数限制。
三、优化目标函数设计:你到底想要什么?
很多人的目标函数就写一个「最大化收益」。嗯,这很天真。你想想看,一个收益极高但回撤50%的策略,你敢实盘吗?
3.1 常见目标函数
| 目标函数 | 公式 | 适用场景 |
|---|---|---|
| 夏普比率 | (Rp - Rf) / σp | 通用,追求风险调整后收益 |
| 卡玛比率 | 年化收益 / 最大回撤 | 厌恶回撤的投资者 |
| 索提诺比率 | (Rp - Rf) / σd | 只关注下行风险 |
| 收益-回撤比 | 总收益 / 最大回撤 | 短线高频策略 |
3.2 我的设计哲学
我个人习惯用复合目标函数。比如:
def objective_function(returns, max_drawdown, win_rate):
sharpe = calculate_sharpe(returns)
calmar = calculate_calmar(returns, max_drawdown)
# 给不同指标加权
score = 0.5 * sharpe + 0.3 * calmar + 0.2 * win_rate
return score
四、参数空间与搜索策略:怎么找到最优解?
参数空间,就是所有可能参数取值的集合。搜索策略,就是你怎么在这个空间里「挖宝」。
4.1 参数空间的维度诅咒
假设你有5个参数,每个参数取10个值,那总组合就是10^5 = 10万种。如果每个组合回测需要1秒,那就是27.8小时。嗯,你不可能等那么久。
4.2 搜索策略对比
| 搜索策略 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 网格搜索 | 简单、可并行 | 维度灾难 | 参数少于3个 |
| 随机搜索 | 效率高、覆盖广 | 可能错过最优 | 参数5-10个 |
| 贝叶斯优化 | 智能、样本效率高 | 实现复杂 | 参数10个以上 |
| 遗传算法 | 全局搜索能力强 | 收敛慢 | 非凸优化问题 |
4.3 我的实战选择
在暗池策略优化中,我通常先用随机搜索跑1000次,找到几个「热点区域」,然后用贝叶斯优化在这些区域精细搜索。这样既快又准。
from skopt import gp_minimize
def optimize_strategy(params_space):
# 先用随机搜索粗筛
random_results = random_search(params_space, n_iter=1000)
# 找到top 10%的区域
top_regions = identify_hotspots(random_results)
# 贝叶斯精细搜索
best_params = gp_minimize(
objective_function,
top_regions,
n_calls=200,
random_state=42
)
return best_params
知识体系总览
下面这张图,是我对本章知识体系的总结。你可以把它当成一张「作战地图」:
这张图把四个核心概念串起来了。你从中心出发,沿着四条分支往下走,就能看到每个模块的关键要素。我个人习惯把它打印出来贴在工位上,做优化时随时对照。