
ZSCORE, RSI, ATR, SMA, EMA
Il ne s’agit pas d’une stratégie de suivi de tendance ordinaire. La stratégie d’arbitrage statistique XAG/XAU est basée sur une hypothèse centrale: le prix de l’or et de l’argent a une relation de retour à la valeur moyenne à long terme. Lorsque le Z-Score atteint un écart de ±2, le prix s’écarte de la valeur maximale en termes statistiques, ce qui permet de saisir les opportunités de retour.
Le cœur de la stratégie est de construire un modèle de rapport de prix normalisé. Le rapport est normalisé pour XAG et XAU à 20 cycles, puis calculé et lissé avec un EMA à 3 cycles. Ce traitement est plus stable qu’un simple rapport de prix et filtre efficacement le bruit à court terme.
Contrairement aux signaux traditionnels de sur-achat et de survente au RSI, le RSI = 50 est utilisé comme condition de filtrage à vide. Le RSI < 50 permet de faire plus et le RSI > 50 permet de faire moins. La logique de conception est claire: acheter en attendant un rebond lorsqu’il est relativement faible et vendre en attendant un rebond lorsqu’il est relativement fort. Ce mécanisme de filtrage réduit efficacement le risque de négociation en contrepartie et améliore la qualité du signal.
Le stop-loss est réglé à 3 fois l’ATR, le stop-loss à 8 fois l’ATR, et le rapport risque/bénéfice est de 1:2,67. Cette conception est basée sur le caractère de l’arbitrage statistique: la probabilité de retour à la moyenne est élevée, mais il faut donner suffisamment de marge d’erreur. L’ATR à 14 cycles assure que le niveau de stop-loss s’adapte aux changements de volatilité du marché.
La stratégie d’arbitrage statistique fonctionne mieux dans les situations de volatilité horizontale, car les caractéristiques de retour des valeurs moyennes sont plus évidentes. Dans les marchés à tendance unilatérale, les prix peuvent s’écarter de la valeur moyenne pendant une longue période, ce qui entraîne un risque de retrait plus élevé. Il est recommandé de l’utiliser lorsque la volatilité du marché est modérée et qu’il n’y a pas de tendance unilatérale évidente.
Les relations statistiques historiques ne garantissent pas la continuité future. Le rapport prix-or-argent peut être dévié à long terme en raison de facteurs tels que les changements de la structure de l’offre et de la demande, les différences de politique monétaire. La stratégie présente un risque de perte continue, en particulier pendant les changements structurels du marché. Il est recommandé de mettre en œuvre une gestion des risques stricte, de contrôler le risque de transaction unique ne dépassant pas 2% des fonds du compte et d’évaluer régulièrement l’efficacité de la stratégie.
//@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")