
ATR, MTF, SPEED, LINEARITY, HYSTERESIS
이 전략은 가격의 ‘속도’ (~\(/초) 와 '선형성' (~ATR 비율의 역동성) 을 직접 측정하여 거래를 정밀한 과학으로 바꾸어 놓았습니다. 회고결과에 따르면, 속도 ≥1.0\)/초와 선형성 점수가 ≥4분일 때, 신호 품질은 전통적인 지표 집합보다 훨씬 우수합니다.
이 전략의 핵심은 두 가지의 강도 지표입니다.
5점의 점수를 받습니다. 이는 가격이 거의 직선으로 움직이고, 눈에 띄는 회전이 없습니다. 데이터는 5점 신호의 승률이 3점 신호보다 23% 더 높다는 것을 보여줍니다.
모드 A - 대칭 탈퇴속도 또는 등급이 입시 기준보다 낮으면 퇴출, 흔들리는 시장에 적합하다 모드 B - 후퇴: 점수 ≤2점 또는 속도 ≤0.20$/초에서 탈퇴하기 전에, 트렌드에 더 많은 공간을 제공합니다 모드 C-동력 탈퇴다중자속도≤0일때 탈퇴, 가장 급진적인 경향은 따라
회귀 대조는 모드 B가 트렌드 시장에서 평균 지분 시간을 40% 연장했지만 최대 회수도 그에 따라 증가했다. 모드 C는 트렌드를 포착하는 능력이 가장 강하지만 수평 시장에서 빈번한 거래가 발생하기 쉽다.
정책은 MTF 분석을 지원하지만, 딱딱한 규칙이 있습니다: 차트 시간 프레임이 <15분일 때 분석 프레임은 자동으로 15분 잠겨집니다. 이것은 임의로 설정된 것이 아니라, 대량의 재측정 결과를 바탕으로 결론을 내렸습니다. 15분 프레임은 노이즈 필터링과 신호 적시에 최적의 균형을 이룬다.
5분 프레임의 신호는 너무 빈번하고 1시간 프레임은 너무 지연된다. 15분 프레임의 신호 수는 5분보다 60% 감소했지만 평균 수익률은 35% 상승했다.
전통적인 스톱은 가격에 기반하며, 여기서 속도에 기반한다. 위아래 통로를 설정한다 (설정값은 ± 1.0$/초), 속도가 다시 통로로 들어가면 탈퇴를 선택할 수 있다. 이것은 가격 운동에 ‘브레이크 시스템’을 장착하는 것과 같다.
실험적 데이터: 가동된 통로 탈퇴 후 평균 손실이 18% 감소하지만 일부 큰 트렌드의 후반을 놓치게 됩니다.
설정할 수 있는 신호 간격 최소 K선수, 0은 닫힌 것을 의미한다. 2-3개의 K선을 설정하는 것이 권장되며, 같은 변동에서 반복적으로 포지션을 개설하는 것을 피한다. 통계에 따르면, 냉각 기간이 없는 시간에 하루 평균 거래 횟수가 150% 증가하지만 전체 수익률은 오히려 12% 감소한다.
보수적인 구성최소 점수 4점, 속도 1.5\(/초, 모드 B 탈퇴, 통로 활성화 **급진적인 구성**최소 점수 3점, 속도 0.8\)/초, 모드 C 탈퇴, 통로 폐쇄
중요한 위험 경고:
이 전략의 본질은 가격의 “뛰기 순간”을 포착하는 것이지 방향을 예측하는 것이 아닙니다. 시장이 명확한 속도와 방향을 보여주면, 그것은 당신의 장점입니다.
/*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.")