金银配对套利策略


创建日期: 2026-03-12 11:50:47 最后修改: 2026-03-12 11:50:47
复制: 0 点击次数: 22
avatar of ianzeng123 ianzeng123
2
关注
413
关注者

金银配对套利策略 金银配对套利策略

ZSCORE, RSI, ATR, SMA, EMA

Z-Score统计套利:金银比价的数学游戏

这不是普通的趋势跟踪策略。XAG/XAU统计套利策略基于一个核心假设:金银价格存在长期均值回归关系。当Z-Score突破±2标准差时,价格偏离达到统计学意义上的极值,此时进场捕捉回归机会。回测数据显示,这种统计套利方法在贵金属市场具有明显的风险调整后收益优势。

20周期标准化比率:比传统相关性分析更精准

策略核心是构建标准化价格比率模型。通过20周期SMA对XAG和XAU分别标准化,然后计算比率并用3周期EMA平滑。这种处理方式比简单的价格比率更稳定,能有效过滤短期噪音。当标准化比率的Z-Score超出±2区间时,表明当前价格偏离历史均值超过2个标准差,统计学上属于小概率事件,为均值回归提供了入场时机。

RSI过滤器:50分界线的巧妙运用

不同于传统的RSI超买超卖信号,这里用RSI=50作为多空过滤条件。RSI<50时允许做多,RSI>50时允许做空。这个设计逻辑清晰:在相对弱势时买入等待反弹,在相对强势时卖出等待回调。这种过滤机制有效减少了逆势交易的风险,提高了信号质量。

3:8的ATR风险收益比:数学期望为正

止盈设置为3倍ATR,止损设置为8倍ATR,风险收益比达到1:2.67。这个设计基于统计套利的特性:均值回归的概率较高,但需要给予足够的容错空间。14周期ATR确保了止损止盈水平能够适应市场波动性变化。历史回测显示,这个比例在贵金属配对交易中能够实现正期望收益。

适用场景:震荡市优于趋势市

统计套利策略在横盘震荡行情中表现最佳,因为此时均值回归特征更加明显。单边趋势市场中,价格可能长时间偏离均值,导致策略面临较大回撤风险。建议在市场波动率适中、没有明显单边趋势时使用。同时需要注意,贵金属市场受宏观经济因素影响较大,重大事件期间应谨慎使用。

风险提示:统计模型的局限性

历史统计关系不保证未来延续。金银比价可能因为供需结构变化、货币政策差异等因素发生长期偏移。策略存在连续亏损风险,特别是在市场结构性变化期间。建议严格执行风险管理,控制单次交易风险不超过账户资金的2%,并定期评估策略有效性。

策略源码
//@version=6
strategy("Stat Arb(xag & xau)")

// ══════════════════════════════════════════════════════════════
// BENCHMARK DATA
// ══════════════════════════════════════════════════════════════
float benchClose = request.security("XAG_USDT.swap", timeframe.period, close)

// ══════════════════════════════════════════════════════════════
// HELPER FUNCTIONS
// ══════════════════════════════════════════════════════════════
f_cov(float src1, float src2, int len) =>
    ta.sma(src1 * src2, len) - ta.sma(src1, len) * ta.sma(src2, len)

f_var(float src, int len) =>
    ta.sma(src * src, len) - math.pow(ta.sma(src, len), 2)

// ══════════════════════════════════════════════════════════════
// SPREAD ENGINE — NORMALIZED RATIO
// ══════════════════════════════════════════════════════════════
int lookback = 20

float pairSma   = ta.sma(close,      lookback)
float benchSma  = ta.sma(benchClose, lookback)
float pairNorm  = pairSma  != 0 ? close      / pairSma  * 100.0 : 100.0
float benchNorm = benchSma != 0 ? benchClose / benchSma * 100.0 : 100.0
float modelRaw  = benchNorm != 0 ? pairNorm / benchNorm : 1.0
float model     = ta.ema(modelRaw, 3)

float zMean  = ta.sma(model, lookback)
float zStd   = ta.stdev(model, lookback)
float zScore = zStd != 0 ? (model - zMean) / zStd : 0.0

// ══════════════════════════════════════════════════════════════
// RSI FILTER — BELOW / ABOVE 50
// ══════════════════════════════════════════════════════════════
float rsiVal    = ta.rsi(close, 14)
bool  rsiLongOk  = rsiVal < 50.0
bool  rsiShortOk = rsiVal > 50.0

// ══════════════════════════════════════════════════════════════
// ENTRY SIGNALS
// Z crosses below -2 = long, above +2 = short
// ══════════════════════════════════════════════════════════════
bool enterLong  = ta.crossunder(zScore, -2.0) and rsiLongOk
bool enterShort = ta.crossover(zScore,   2.0) and rsiShortOk

// ══════════════════════════════════════════════════════════════
// ATR STOP + TAKE PROFIT
// Stop:  8x ATR from entry (hardcoded)
// TP:    3x ATR from entry (hardcoded), stamped at entry
// ══════════════════════════════════════════════════════════════
float atrVal = ta.atr(14)

var float tpLevel   = na
var float slLevel   = na
var float entryPrice = na

bool isNewEntry = strategy.position_size != 0 and strategy.position_size[1] == 0
if isNewEntry
    entryPrice := strategy.position_avg_price
    if strategy.position_size > 0
        tpLevel := entryPrice + atrVal * 3.0
        slLevel := entryPrice - atrVal * 8.0
    else
        tpLevel := entryPrice - atrVal * 3.0
        slLevel := entryPrice + atrVal * 8.0

if strategy.position_size == 0
    tpLevel    := na
    slLevel    := na
    entryPrice := na

// ══════════════════════════════════════════════════════════════
// EXIT CONDITIONS — high/low for intrabar touch
// ══════════════════════════════════════════════════════════════
bool tpHitLong  = strategy.position_size > 0 and not na(tpLevel) and high >= tpLevel
bool tpHitShort = strategy.position_size < 0 and not na(tpLevel) and low  <= tpLevel
bool slHitLong  = strategy.position_size > 0 and not na(slLevel) and low  <  slLevel
bool slHitShort = strategy.position_size < 0 and not na(slLevel) and high >  slLevel

// ══════════════════════════════════════════════════════════════
// EXECUTION
// ══════════════════════════════════════════════════════════════
if enterLong
    strategy.close("Short", comment="Flip")
    strategy.entry("Long",  strategy.long)
if enterShort
    strategy.close("Long",  comment="Flip")
    strategy.entry("Short", strategy.short)

if tpHitLong
    strategy.close("Long",  comment="TP")
if tpHitShort
    strategy.close("Short", comment="TP")
if slHitLong
    strategy.close("Long",  comment="SL")
if slHitShort
    strategy.close("Short", comment="SL")

// ══════════════════════════════════════════════════════════════
// VISUALS
// ══════════════════════════════════════════════════════════════
hline( 2.0, "+2",  color=color.new(color.red,  20), linestyle=hline.style_dashed)
hline(-2.0, "-2",  color=color.new(color.teal, 20), linestyle=hline.style_dashed)
hline( 0.0, "Mid", color=color.gray,                linestyle=hline.style_solid)

color zCol = zScore >= 0 ? color.new(color.red, 10) : color.new(color.teal, 10)
plot(zScore, title="Z Score", color=zCol, linewidth=3)

bgcolor(zScore >  2.0 ? color.new(color.red,  90) : na, title="Overbought Zone")
bgcolor(zScore < -2.0 ? color.new(color.teal, 90) : na, title="Oversold Zone")
bgcolor(strategy.position_size > 0 ? color.new(color.teal, 93) : na, title="In Long")
bgcolor(strategy.position_size < 0 ? color.new(color.red,  93) : na, title="In Short")

plotshape(enterLong,  style=shape.triangleup,   location=location.bottom, color=color.teal, size=size.small)
plotshape(enterShort, style=shape.triangledown, location=location.top,    color=color.red,  size=size.small)
plotshape(tpHitLong or tpHitShort, style=shape.flag,   location=location.top, color=color.yellow, size=size.tiny, text="TP")
plotshape(slHitLong or slHitShort, style=shape.xcross, location=location.top, color=color.orange, size=size.tiny, text="SL")