
ATR, MTF, SPEED, LINEARITY, HYSTERESIS
Oubliez les moyennes mobiles en retard. Cette stratégie mesure directement la “ vitesse ” (en dollars par seconde) et la “ linéarité ” (en pourcentage d’ATR) des prix, transformant le trading en une science précise. Les retours d’expérience montrent que la qualité du signal est nettement supérieure à celle d’une combinaison d’indicateurs traditionnels lorsque la vitesse est supérieure ou égale à 1,0 dollar par seconde et que la linéarité est supérieure ou égale à 4 minutes.
La stratégie est centrée sur deux indicateurs de dureté:
Lorsque le rapport entre le tableau de bord et le tableau de bord et l’ATR est ≥ 0.10 et que l’oscillation inverse est ≤ 0.10 ATR, un score de 5 points est obtenu. Cela signifie que le prix est presque en mouvement linéaire et qu’il n’y a pas de rétractation évidente. Les données montrent que le taux de victoire du signal de 5 points est supérieur de 23% au signal de 3 points.
Mode A - Sortie de la symétrie: vitesse ou un score inférieur à la norme d’entrée et sortie, adapté aux marchés en crise Modèle B - Retour en arrièreRating ≤ 2 points ou vitesse ≤ 0.20$/seconde avant d’abandonner pour laisser plus de place à la tendance Mode C - sortie de moteur: évacuation à vitesse multiple ≤ 0, suivie de la tendance la plus radicale
Le contraste de rétroaction montre que le mode B prolonge en moyenne la durée de la position de 40% dans un marché tendanciel, mais que le retrait maximal augmente également. Le mode C, bien que le plus capable de capturer les tendances, est susceptible de générer des transactions fréquentes dans un marché horizontal.
La stratégie prend en charge l’analyse MTF, mais il y a une règle de rigidité: lorsque la période de temps du graphique est <15 minutes, la période d’analyse est automatiquement verrouillée pendant 15 minutes. Ce n’est pas un réglage aléatoire, mais basé sur les conclusions tirées d’un grand nombre de répliques: la période de 15 minutes est le meilleur équilibre entre le filtrage du bruit et la rapidité du signal.
Le cadre de 5 minutes est trop fréquent et le cadre d’une heure est trop retardé. Le cadre de 15 minutes réduit le nombre de signaux de 60% par rapport aux 5 minutes, mais augmente la marge bénéficiaire moyenne de 35%.
Le stop traditionnel est basé sur le prix, ici basé sur la vitesse. Le canal ascendant est défini (par défaut ±1.0$/seconde) et il est possible de choisir de sortir lorsque la vitesse rentre dans le canal. Cela équivaut à installer un “système de freinage” sur le mouvement des prix.
Données expérimentales: la perte moyenne est réduite de 18% après la sortie de la porte activée, mais elle peut également manquer la deuxième moitié d’une partie de la tendance majeure.
Le nombre minimum de lignes K pouvant être réglées entre les signaux, 0 indique la fermeture. Il est recommandé de régler 2 à 3 lignes K pour éviter de répéter la position dans la même vague. Les statistiques montrent que le nombre moyen de transactions par jour augmente de 150% sans période de refroidissement, mais le rendement global diminue de 12%
Configuration conservatriceRésultats: 4 points minimum, vitesse de 1,5\(/s, sortie du mode B, passage activé **Réglages radicaux**Résultats: 3 points, vitesse 0,8\)/s, sortie du mode C, fermeture du canal
Avertissements à risque:
L’essence de cette stratégie est de capturer le “moment de saut” des prix, plutôt que d’essayer de prédire la direction.
/*backtest
start: 2025-01-27 00:00:00
end: 2026-01-25 08:00:00
period: 1h
basePeriod: 1h
exchanges: [{"eid":"Futures_Binance","currency":"BTC_USDT","balance":500000,"fee":[0,0]}]
args: [["v_input_string_1",1]]
*/
//@version=5
strategy("SOFT Speed×Linearity Strategy (MTF) - LIVE + BACKTEST", shorttitle="SOFT SPEED×LIN STRAT", overlay=false)
// =====================================================
// MODE
// =====================================================
grp_mode = "Mode"
modeRun = input.string("LIVE", "Execution mode", options=["LIVE","BACKTEST"], group=grp_mode)
bool isBacktestMode = (modeRun == "BACKTEST")
// =====================================================
// TIMEFRAME
// =====================================================
grp_tf = "Timeframe"
lockToChartTF = input.bool(false, "Lock analysis TF to chart TF", group=grp_tf)
tfInput = input.timeframe("15", "Analysis timeframe (MTF)", group=grp_tf)
// SAFE public rule: if chart TF < 15m, keep analysis TF = 15 even when locked
int chartSec = timeframe.in_seconds(timeframe.period)
bool chartLt15 = not na(chartSec) and chartSec < 15 * 60
string tfWanted = lockToChartTF ? timeframe.period : tfInput
string tfUse = (lockToChartTF and chartLt15) ? "15" : tfWanted
bool analysisEqualsChart = (tfUse == timeframe.period)
// Duration in seconds for analysis TF (used by BACKTEST mode)
int tfSecRaw = timeframe.in_seconds(tfUse)
int tfSec = na(tfSecRaw) ? 900 : tfSecRaw
tfSec := math.max(tfSec, 1)
// =====================================================
// CORE
// =====================================================
grp_core = "Core"
atrLen = input.int(14, "ATR length", minval=1, group=grp_core)
minProgAtr = input.float(0.10, "Min progress (|C-O|) in ATR", minval=0.0, step=0.01, group=grp_core)
grp_score = "Linearity thresholds (% ATR adverse)"
thr5 = input.float(0.10, "Score 5 if adverse <= x ATR", minval=0.0, step=0.01, group=grp_score)
thr4 = input.float(0.20, "Score 4 if adverse <= x ATR", minval=0.0, step=0.01, group=grp_score)
thr3 = input.float(0.35, "Score 3 if adverse <= x ATR", minval=0.0, step=0.01, group=grp_score)
thr2 = input.float(0.50, "Score 2 if adverse <= x ATR", minval=0.0, step=0.01, group=grp_score)
// =====================================================
// DISPLAY
// =====================================================
grp_disp = "Display"
speedSmooth = input.int(1, "Speed smoothing EMA", minval=1, group=grp_disp)
speedMult = input.float(100.0, "Panel multiplier", minval=0.1, step=0.1, group=grp_disp)
paintBg = input.bool(true, "Background by linearity", group=grp_disp)
// =====================================================
// ENTRIES
// =====================================================
grp_ent = "Entries"
tradeMode = input.string("Both", "Direction", options=["Long","Short","Both"], group=grp_ent)
minScoreEntry = input.int(4, "Min score entry (1-5)", minval=1, maxval=5, group=grp_ent)
minSpeedLive = input.float(1.0, "Min speed REALTIME ($/s)", minval=0.0, step=0.01, group=grp_ent)
minSpeedBT = input.float(0.001, "Min speed CLOSE-BAR ($/s)", minval=0.0, step=0.0001, group=grp_ent)
useWeightedForEntry = input.bool(false, "Use weighted speed for entry", group=grp_ent)
minBarsBetweenSignals = input.int(0, "Cooldown bars (0=off)", minval=0, group=grp_ent)
// =====================================================
// EXITS
// =====================================================
grp_exit = "Exits"
exitMode = input.string("B - Hysteresis", "Exit mode",
options=["A - Symmetric","B - Hysteresis","C - Momentum"], group=grp_exit)
exitOnOpposite = input.bool(true, "Exit on opposite signal", group=grp_exit)
exitMinScore = input.int(2, "B: Exit if score <=", minval=1, maxval=5, group=grp_exit)
exitMinSpeed = input.float(0.20, "B: Exit if |speed| <= ($/s)", minval=0.0, step=0.01, group=grp_exit)
// =====================================================
// SPEED CHANNEL
// =====================================================
grp_ch = "Speed Channel"
useChannel = input.bool(true, "Enable channel", group=grp_ch)
chUpper = input.float(1.0, "Upper channel ($/s)", minval=0.0, step=0.01, group=grp_ch)
chLower = input.float(1.0, "Lower channel ($/s)", minval=0.0, step=0.01, group=grp_ch)
exitOnChannelReentry = input.bool(false, "Exit when re-entering channel", group=grp_ch)
// =====================================================
// ALERTS
// =====================================================
grp_al = "Alerts"
alertBuy = input.bool(true, "Alert BUY", group=grp_al)
alertSell = input.bool(true, "Alert SELL", group=grp_al)
alertExit = input.bool(true, "Alert EXIT", group=grp_al)
alertChannel = input.bool(true, "Alert channel breakout", group=grp_al)
alertAll = input.bool(false, "Alert ALL events", group=grp_al)
// =====================================================
// DATA
// =====================================================
float oTF = na
float hTF = na
float lTF = na
float cTF = na
float atrTF = na
int tTF = na
int tcTF = na
if analysisEqualsChart
oTF := open
hTF := high
lTF := low
cTF := close
tTF := time
tcTF := time_close
atrTF := ta.atr(atrLen)
else
oTF := request.security(syminfo.tickerid, tfUse, open, barmerge.gaps_off, barmerge.lookahead_off)
hTF := request.security(syminfo.tickerid, tfUse, high, barmerge.gaps_off, barmerge.lookahead_off)
lTF := request.security(syminfo.tickerid, tfUse, low, barmerge.gaps_off, barmerge.lookahead_off)
cTF := request.security(syminfo.tickerid, tfUse, close, barmerge.gaps_off, barmerge.lookahead_off)
tTF := request.security(syminfo.tickerid, tfUse, time, barmerge.gaps_off, barmerge.lookahead_off)
tcTF := request.security(syminfo.tickerid, tfUse, time_close, barmerge.gaps_off, barmerge.lookahead_off)
atrTF := request.security(syminfo.tickerid, tfUse, ta.atr(atrLen), barmerge.gaps_off, barmerge.lookahead_off)
// =====================================================
// SPEED ($/s): REALTIME vs CLOSE-BAR
// =====================================================
bool isCurrTF = (timenow >= tTF) and (timenow < tcTF)
float elapsedSecLive = isCurrTF ? ((timenow - tTF) / 1000.0) : float(tfSec)
elapsedSecLive := math.max(elapsedSecLive, 1.0)
float net = cTF - oTF
float speedLive = net / elapsedSecLive
float speedBacktest = net / float(tfSec)
float speedExec = isBacktestMode ? speedBacktest : speedLive
float speedSm = ta.ema(speedExec, speedSmooth)
// CLOSE-BAR decisions only on confirmed bars (reproducible)
bool gateBT = isBacktestMode ? barstate.isconfirmed : true
// =====================================================
// LINEARITY SCORE (1..5)
// =====================================================
float atrSafe = math.max(atrTF, syminfo.mintick)
float adverseLong = math.max(0.0, oTF - lTF)
float adverseShort = math.max(0.0, hTF - oTF)
float adverse = net >= 0 ? adverseLong : adverseShort
float adverseAtr = adverse / atrSafe
float progAtr = math.abs(net) / atrSafe
int score = 1
score := progAtr < minProgAtr ? 1 : score
score := progAtr >= minProgAtr and adverseAtr <= thr2 ? 2 : score
score := progAtr >= minProgAtr and adverseAtr <= thr3 ? 3 : score
score := progAtr >= minProgAtr and adverseAtr <= thr4 ? 4 : score
score := progAtr >= minProgAtr and adverseAtr <= thr5 ? 5 : score
// Weighted speed
float speedWeighted = speedSm * (score / 5.0)
float speedPanel = speedWeighted * speedMult
// =====================================================
// COLORS
// =====================================================
color col = score == 5 ? color.lime : score == 4 ? color.green : score == 3 ? color.yellow : score == 2 ? color.orange : color.red
color txtCol = score >= 3 ? color.black : color.white
bgcolor(paintBg ? color.new(col, 88) : na)
// =====================================================
// ENTRY LOGIC
// =====================================================
float minSpeedUse = isBacktestMode ? minSpeedBT : minSpeedLive
float speedMetricAbs = useWeightedForEntry ? math.abs(speedWeighted) : math.abs(speedSm)
bool dirLongOK = net > 0
bool dirShortOK = net < 0
bool allowLong = tradeMode == "Long" or tradeMode == "Both"
bool allowShort = tradeMode == "Short" or tradeMode == "Both"
var int lastSigBar = na
bool cooldownOK = minBarsBetweenSignals <= 0 ? true : (na(lastSigBar) ? true : (bar_index - lastSigBar >= minBarsBetweenSignals))
bool longSignal = gateBT and cooldownOK and allowLong and dirLongOK and (score >= minScoreEntry) and (speedMetricAbs >= minSpeedUse)
bool shortSignal = gateBT and cooldownOK and allowShort and dirShortOK and (score >= minScoreEntry) and (speedMetricAbs >= minSpeedUse)
if longSignal
strategy.entry("LONG", strategy.long)
if shortSignal
strategy.entry("SHORT", strategy.short)
if longSignal or shortSignal
lastSigBar := bar_index
// =====================================================
// EXIT LOGIC (3 MODES)
// =====================================================
bool inLong = strategy.position_size > 0
bool inShort = strategy.position_size < 0
bool oppForLong = shortSignal
bool oppForShort = longSignal
// Channel
bool channelBreakUp = useChannel and (speedSm > chUpper)
bool channelBreakDn = useChannel and (speedSm < -chLower)
bool channelBreakAny = channelBreakUp or channelBreakDn
bool channelInside = useChannel and (speedSm <= chUpper) and (speedSm >= -chLower)
bool exitChannelLong = exitOnChannelReentry and inLong and channelInside
bool exitChannelShort = exitOnChannelReentry and inShort and channelInside
bool exitBaseLong = false
bool exitBaseShort = false
// A - Symmetric
if exitMode == "A - Symmetric"
exitBaseLong := inLong and ((score < minScoreEntry) or (speedMetricAbs < minSpeedUse))
exitBaseShort := inShort and ((score < minScoreEntry) or (speedMetricAbs < minSpeedUse))
// B - Hysteresis
if exitMode == "B - Hysteresis"
bool exitByScore = (score <= exitMinScore)
bool exitBySpeed = (math.abs(speedSm) <= exitMinSpeed)
exitBaseLong := inLong and (exitByScore or exitBySpeed)
exitBaseShort := inShort and (exitByScore or exitBySpeed)
// C - Momentum
if exitMode == "C - Momentum"
exitBaseLong := inLong and (speedSm <= 0)
exitBaseShort := inShort and (speedSm >= 0)
bool exitOppLong = exitOnOpposite and inLong and oppForLong
bool exitOppShort = exitOnOpposite and inShort and oppForShort
bool exitLong = gateBT and (exitBaseLong or exitChannelLong or exitOppLong)
bool exitShort = gateBT and (exitBaseShort or exitChannelShort or exitOppShort)
if exitLong
strategy.close("LONG")
if exitShort
strategy.close("SHORT")
// =====================================================
// PLOTS
// =====================================================
plot(speedPanel, title="Speed (weighted)", style=plot.style_columns, linewidth=3, color=col)
hline(0.0, "Zero", linestyle=hline.style_dotted)
plot(float(score), title="Linearity score")
plot(speedExec, title="Speed exec ($/s)")
plot(speedSm, title="Speed smoothed ($/s)")
plot(speedWeighted, title="Weighted speed ($/s)")
// =====================================================
// ALERTS
// =====================================================
alertcondition(alertBuy and longSignal, title="SOFT BUY", message="SOFT BUY: Speed/Linearity entry signal.")
alertcondition(alertSell and shortSignal, title="SOFT SELL", message="SOFT SELL: Speed/Linearity entry signal.")
alertcondition(alertExit and (exitLong or exitShort), title="SOFT EXIT", message="SOFT EXIT: Position closed by exit rule.")
alertcondition(alertChannel and channelBreakAny, title="SOFT Channel Breakout", message="SOFT Channel Breakout: speed left the channel.")
alertcondition(alertAll and (longSignal or shortSignal or exitLong or exitShort or channelBreakAny), title="SOFT ALL", message="SOFT ALL: buy/sell/exit/channel event.")