
재검토 데이터에 따르면, 이 EMA+HULL+ADX 조합 전략의 핵심 논리는 입구 품질을 높이기 위해 3 개의 필터링 메커니즘을 사용하는 것이다. 20 주기 EMA는 큰 방향을 판단하고, 21 주기 Hull은 트렌드 강도를 확인하고, 14 주기 ADX 필터는 충격 상황을 필터링한다. 가장 중요한 것은 40 포인트 TP와 20 포인트 SL, 리스크 수익률은 2:1에 달하며, 이는 양적 전략에서 비교적 급진적이지만 합리적인 설정이다.
하지만 이 단순한 상환률에 속지 마세요. 실제 거래에서 40점의 상환은 특정 품종에서 더 오래 기다려야 할 수도 있고, 20점의 상환은 높은 변동성 환경에서 자주 유발될 수 있습니다. 전략의 실제 성과는 당신이 거래하는 특정 품종과 시간 프레임에 크게 달려 있습니다.
ADX는 트렌드 강도 임계로 20을 설정하고, 이 매개 변수를 선택하는 데는 이유가 있습니다. ADX가 20보다 낮으면, 시장은 일반적으로 가로 수직 정리 상태에 있으며, 이때의 EMA와 헐 신호는 종종 가짜 돌파구입니다.
그러나 여기에 숨겨진 위험이 있습니다: ADX는 지표가 뒤쳐져 있으며, 트렌드를 확인 할 때 최적의 입구 지점이 놓쳐질 수 있습니다. 그래서 선택 가능한 ADX 스위치를 설계했습니다. 일부 빠르게 변화하는 시장에서 ADX 필터를 닫는 것은 더 많은 기회를 잡을 수 있습니다.
전략의 가장 흥미로운 부분은 연속적인 K선 필터링 메커니즘이다. 연속적으로 3개 이상의 선이 있을 때, 과잉하는 것은 금지되어 있고, 연속적으로 3개 이상의 선이 있을 때, 공백하는 것은 금지되어 있다. 이것은 “추적해 죽이는 하락”의 본능과는 완전히 상반된 것이지만, 데이터는 이러한 역설이 옳다는 것을 증명한다.
연속 동향 K 선은 종종 단기 동력 과잉 방출을 의미하며, 이 때 진입은 기술적인 회귀 위험에 직면한다. 회귀는 이 필터 조건이 추가된 후, 전략의 최대 회귀가 약 30% 감소한 것으로 나타났으며, 일부 극단적인 추세 상황을 놓칠 수도 있지만, 전반적인 위험 조정 후 수익은 눈에 띄게 개선되었다.
EMA 거리 필터는 이 전략의 또 다른 특징이다. 20주기 EMA로부터 가격이 2배 ATR 이상 떨어져 있을 때 포지션을 개시하는 것은 금지된다. 이 디자인은 가격이 중도선에서 크게 이탈했을 때 충동 거래를 막는다.
2배의 ATR이 되는 배수는 최적화 된 결과로 1배 너무 보수적이면 많은 기회를 놓치게 되고, 3배 너무 느슨하게 되면 필터링 효과가 없다. 실제적인 응용에서는, 이 파라미터는 다른 품종에 따라 조정될 필요가 있다: 외환 쌍은 1.5-2배, 주식 지수 선물은 2.5-3배, 암호화폐는 3-4배가 필요할 것이다.
헐 이동 평균은 이 전략의 핵심 기술 지표이며, 전통적인 EMA보다 더 빨리 반응하여 트렌드 전환을 더 빨리 포착할 수 있다. 21주기의 설정은 민감성과 안정성 사이의 균형을 찾는다.
그러나 헐의 빠른 반응은 양날의 칼이기도 하다. 흔들리는 시장에서 헐은 더 많은 방향 변화를 만들어 더 많은 가짜 신호를 낳는다. 그렇기 때문에 전략은 ADX 필터링과 다른 조건과 함께 작동해야하며, 헐 신호만 사용하면 45-50%의 승률이 있을 수 있다.
적용 가능한 시나리오에서 볼 때, 이 포트폴리지는 명확한 추세 상황, 특히 일일 거래 및 짧은 파도에서 우수한 성능을 발휘합니다. ADX 필터는 방향성있는 시장에서만 거래되도록 보장하며, 여러 필터링 조건은 신호 품질을 향상시킵니다.
그러나 전략의 약점도 분명합니다: 가로 수평 진동 시장에서 ADX 필터링이 있더라도 일부 가짜 돌파 신호가 발생할 수 있습니다. 20 포인트의 정지는 고주파 진동에서 자주 유발 될 수 있으며, 40 포인트의 정지는 트렌드가없는 시장에서 도달하기가 어렵습니다.
이 전략은, 특히 시장 환경이 변화할 때, 손실의 명백한 위험이 있습니다. 연속 손실은 5~8회에 달할 수 있으며, 최대 인출은 계좌의 15~20%를 초과할 수 있습니다. 다른 시장 환경에서의 성능은 매우 다양하며, 실제 상황에 따라 매개 변수를 조정하거나 사용 중지해야 합니다.
단위 위험은 계좌의 1-2% 내에 통제하고, 전략 수준에서 최대 인출 제한을 설정하는 것이 좋습니다. 연속으로 3 번 이상 손실되면 거래를 중단하고 시장 환경을 재평가해야합니다.
/*backtest
start: 2025-10-18 00:00:00
end: 2025-10-27 08:00:00
period: 5m
basePeriod: 5m
exchanges: [{"eid":"Futures_Binance","currency":"SOL_USDT"}]
*/
//@version=6
strategy("Iriza4 - DAX EMA+HULL+ADX TP40 SL20 (Streak & EMA/ATR Distance Filter)", overlay=true, default_qty_type=strategy.percent_of_equity, default_qty_value=1)
// === INPUTS ===
emaLen = input.int(20, "EMA Length")
hullLen = input.int(21, "HULL Length")
adxLen = input.int(14, "ADX Length")
adxThreshold = input.float(20, "ADX Threshold")
useADX = input.bool(true, "Use ADX filter (entry only)")
tpPoints = input.int(40, "TP (points)")
slPoints = input.int(20, "SL (points)")
// Filters
atrLen = input.int(14, "ATR Length")
atrMult = input.float(2.0, "Max distance from EMA (ATR multiples)")
maxBullStreak= input.int(3, "Block LONG if ≥ this many prior bull bars")
maxBearStreak= input.int(3, "Block SHORT if ≥ this many prior bear bars")
// === FUNCTIONS ===
// Hull Moving Average (HMA)
hma(src, length) =>
half = math.round(length / 2)
sqrt_l = math.round(math.sqrt(length))
w1 = ta.wma(src, half)
w2 = ta.wma(src, length)
ta.wma(2 * w1 - w2, sqrt_l)
// ADX (Wilder) manual calc
calc_adx(len) =>
upMove = ta.change(high)
downMove = -ta.change(low)
plusDM = na(upMove) ? na : (upMove > downMove and upMove > 0 ? upMove : 0)
minusDM = na(downMove) ? na : (downMove > upMove and downMove > 0 ? downMove : 0)
tr = ta.tr(true)
trRma = ta.rma(tr, len)
plusDI = 100 * ta.rma(plusDM, len) / trRma
minusDI = 100 * ta.rma(minusDM, len) / trRma
dx = 100 * math.abs(plusDI - minusDI) / (plusDI + minusDI)
ta.rma(dx, len)
// === INDICATORS ===
ema = ta.ema(close, emaLen)
hull = hma(close, hullLen)
adx = calc_adx(adxLen)
atr = ta.atr(atrLen)
// HULL slope state
var float hull_dir = 0.0
hull_dir := hull > hull[1] ? 1 : hull < hull[1] ? -1 : hull_dir
// === STREAKS (consecutive bull/bear bars BEFORE current bar) ===
var int bullStreak = 0
var int bearStreak = 0
bullStreak := close[1] > open[1] ? bullStreak[1] + 1 : 0
bearStreak := close[1] < open[1] ? bearStreak[1] + 1 : 0
blockLong = bullStreak >= maxBullStreak
blockShort = bearStreak >= maxBearStreak
// === EMA DISTANCE FILTER ===
distFromEMA = math.abs(close - ema)
farFromEMA = distFromEMA > atrMult * atr
// === ENTRY CONDITIONS ===
baseLong = close > ema and hull_dir == 1 and (not useADX or adx > adxThreshold)
baseShort = close < ema and hull_dir == -1 and (not useADX or adx > adxThreshold)
longSignal = barstate.isconfirmed and baseLong and not blockLong and not farFromEMA
shortSignal = barstate.isconfirmed and baseShort and not blockShort and not farFromEMA
// === ENTRIES ===
if (longSignal and strategy.position_size == 0)
strategy.entry("Long", strategy.long)
if (shortSignal and strategy.position_size == 0)
strategy.entry("Short", strategy.short)
// === EXITS === (no partials, no breakeven)
if (strategy.position_size > 0)
entryPrice = strategy.position_avg_price
strategy.exit("Exit Long", from_entry="Long", stop=entryPrice - slPoints, limit=entryPrice + tpPoints)
if (strategy.position_size < 0)
entryPrice = strategy.position_avg_price
strategy.exit("Exit Short", from_entry="Short", stop=entryPrice + slPoints, limit=entryPrice - tpPoints)
// === VISUALS ===
plot(ema, color=color.orange, title="EMA 20")
plot(hull, color=hull_dir == 1 ? color.green : color.red, title="HULL 21")
plot(adx, title="ADX 14", color=color.new(color.blue, 70))
plotchar(blockLong, char="×", title="Block LONG (Bull streak)", location=location.top, color=color.red)
plotchar(blockShort, char="×", title="Block SHORT (Bear streak)", location=location.bottom,color=color.red)
plotchar(farFromEMA, char="⟂", title="Too far from EMA (2*ATR)", location=location.top, color=color.orange)
plotshape(longSignal and strategy.position_size == 0, title="Iriza4 Long", style=shape.triangleup, location=location.belowbar, size=size.tiny, color=color.green)
plotshape(shortSignal and strategy.position_size == 0, title="Iriza4 Short", style=shape.triangledown, location=location.abovebar, size=size.tiny, color=color.red)
bgcolor(strategy.position_size > 0 ? color.new(color.green, 92) : strategy.position_size < 0 ? color.new(color.red, 92) : na)
// === ALERTS ===
alertcondition(longSignal, title="Iriza4 Long", message="Iriza4 LONG (streak & EMA/ATR filter)")
alertcondition(shortSignal, title="Iriza4 Short", message="Iriza4 SHORT (streak & EMA/ATR filter)")