黄金期货(日频)策略:趋势动量为核 + 波动率目标 + 宏观状态过滤
1. 交易标的与频率
- 标的:黄金期货主力连续合约 AU_DOMINANT.SHF(主力 / 次主力近似为主连)。
- 频率:日线(handle_data 每日运行),rebalance_interval = 1 表示每天最多调仓一次。
2. 入场信号:动量 + 20 日突破共振
- 策略用两个技术条件来决定做多 / 做空:
时间序列动量(lookback_mom=20):
计算近 20 日收盘价收益:ret = P_t / P_{t-20} - 1。
ret > 0 → 动量看多 mom_dir = +1;ret < 0 → 动量看空 mom_dir = -1;否则 0。 - 20 日价格突破(breakout_window=20):
看过去 20 天的高点/低点(不含今天):recent_high、recent_low。
若:
昨收 <= recent_high 且 今收 > recent_high → 上穿 20 日高点,突破看多 breakout_dir = +1;
昨收 >= recent_low 且 今收 < recent_low → 下破 20 日低点,突破看空 breakout_dir = -1;
否则 breakout_dir = 0。 - 组合信号:
只有当 mom_dir == breakout_dir 且非 0 时才认为趋势方向可靠:
同为 +1 → 做多信号。
同为 -1 → 做空信号。
如两者不一致或其中一个为 0 → signal_dir = 0,不主动开新仓。
3. 仓位大小:目标波动率 + 杠杆控制
- 在得到方向 signal_dir 后,策略按风险预算决定手数:
估算标的波动率(vol_window=20):
取最近 21 个收盘价,算 20 个对数收益的日波动率 daily_vol;
年化波动率:ann_vol = daily_vol * sqrt(252)。 - 计算目标杠杆:
目标年化波动率 target_vol_annual = 20%;
若 ann_vol > 0:leverage = target_vol_annual / ann_vol;
若 ann_vol <= 0:保守地用 leverage = 1;
杠杆上限 max_leverage = 3,故实际 leverage = min(leverage, 3)。 - 换算目标手数:
目标风险资金:target_risk_capital = total_value * leverage(账户总权益 × 杠杆)。
每手名义价值:notional = contract_multiplier * price(合约乘数从 panda_data.get_future_list 获取,失败则默认 1000)。
目标手数:hands = floor(target_risk_capital / notional)。
若手数 <= 0 → 不持仓。 - 方向:
signal_dir = +1/-1 决定多空方向;
组合成目标:(target_side = signal_dir, target_hands)。
4. 宏观过滤:用美元指数限制多头
- 策略引入简单的宏观条件,主要用美元指数 DXY 的动量来过滤多头:
通过 panda_data.get_stock_us_data 获取 DXY.NB 过去约 60 天的日线收盘价。
计算区间收益:ret_DXY = DXY_last / DXY_first - 1。 - 判定:
若 ret_DXY > 0(美元走强):
allow_long = False → 不允许做多黄金;
允许做空(allow_short=True)。
若 ret_DXY <= 0(美元弱):
allow_long = True,允许做多黄金。 - 策略根据 allow_long / allow_short 调整信号:
signal_dir > 0 且 allow_long=False → 直接把信号清零,不开多;
当前代码 allow_short 始终 True,未来可以扩展实际利率等条件限制做空。 - 直观理解:
美元走强 → 通常对黄金不利 → 只做空或观望,不做多黄金;
美元走弱 → 对黄金有利 → 放行多头。
5. 风险控制模块
策略有多层风控:
5.1 单笔 ATR 止损
利用 TR 序列计算 ATR(20 日均值)。
- 开仓时设置止损价:
多仓:stop = entry_price - 2.5 * ATR。
空仓:stop = entry_price + 2.5 * ATR。 - 每个 bar 调用 apply_trade_stop 检查:
若多仓当前价 <= stop 或空仓当前价 >= stop → 触发止损:
平掉所有可平仓位;
打印“触发单笔 ATR 止损”;
清空持仓方向与止损状态。
5.2 最大回撤 + 冷静期
- 在 before_trading 中进行账户级别风控:
跟踪账户权益峰值 equity_peak;
当前回撤 drawdown = (equity_peak - total_value) / equity_peak; - 若 drawdown >= 15% 且尚未进入冷静期:
标记 in_mdd_cooldown = True,
冷静期天数 mdd_cooldown_left = 10;
打印提示。 - 冷静期内每天递减计数,到 0:
退出冷静期,并把当前权益设为新的峰值。
在冷静期中,handle_data 会强制 signal_dir = 0,即不新开仓,仅已有仓按止损/信号逐步退出。
5.3 极端波动过滤
- 利用最近 20 日收益标准差检测异常:
取最近 21 个对数收益,其中前 20 个算历史标准差 hist_std,最后一个为最新日收益 last_ret。
若 |last_ret| > 3 × hist_std:
认为当天是极端波动日,打日志;
extreme_vol_flag=True,并在当日将 signal_dir = 0,即
极端波动日不新开仓。
6. 调仓逻辑:从当前仓位到目标仓位
- trade_to_target 根据目标方向 + 手数进行仓位调整:
读取当前黄金合约的多头和空头数量 current_long / current_short,判断当前方向 curr_side:
仅多头 → curr_side = +1;
仅空头 → curr_side = -1;
无仓 → curr_side = 0。 - 若目标是空仓(target_side=0 或 target_hands=0):
若当前有仓 → 用 close_all 全部平掉,清空止损状态。 - 若当前无仓且目标有仓:
按目标手数直接调用:
buy_open 开多,或
sell_open 开空;
记录 position_side、entry_price 并设置 ATR 止损价。 - 若当前有仓且方向与目标相同:
比较当前手数与目标手数:
手数不足 → 使用 buy_open 或 sell_open 加仓;
手数过多 → 使用 sell_close 或 buy_close 减仓;
调整后重新设置止损价(一种简化处理方式)。 - 若当前有仓且方向与目标相反:
先 close_all 平掉原方向所有仓位;
再按目标方向开仓。
回测结果
整体来看,这次“策略回测结果”表现偏弱,属于 C~D 档水平:策略本身几乎没有收益(年化收益≈0、波动率≈0、最大回撤≈0),但相对上证指数的超额收益为负(alpha≈-4%),信息比率约 -1.37、Sortino 约 -2.26,说明在承担的下行风险下,策略并未获得补偿,反而显著跑输基准。
改进方向
- 降低信号门槛,提升交易参与度:
尝试放宽组合信号规则,例如:动量和突破只要有一个成立就可入场,另一个只做权重调整,而不是完全过滤。
将 lookback_mom、breakout_window 从 20 调整为 10~15 做一组对比,提升信号敏感度。 - 放宽宏观过滤强度:
目前 DXY 只要“走强”就禁止做多,可能长期锁死多头;可改成仅在“极度走强”时才禁止做多,或者只降低仓位,而不是完全禁止。 - 调整风险目标与冷静期:
适当上调 target_vol_annual(如从 20% 测试到 25%~30%)、放宽 portfolio_mdd_limit(如从 15% 到 20%),让策略在可控风险内真正“动起来”,否则长期空仓无法体现策略优势。
缩短 mdd_cooldown_days(比如从 10 改为 3~5),避免一次回撤后长时间完全停摆。 - 增加样本期与测试区间:
用更长历史区间(至少 3~5 年)回测,检验策略在不同利率与美元周期下的表现;
同时在更现实的历史期间(真正存在的黄金期货数据)上测试,以排除“数据不完整/未来时间”的干扰。