双路径趋势捕手

EMA MACD VOLUME PINBAR RESISTANCE
创建日期: 2025-12-17 15:38:19 最后修改: 2025-12-18 15:12:04
复制: 0 点击次数: 8
avatar of ianzeng123 ianzeng123
2
关注
330
关注者

双路径趋势捕手 双路径趋势捕手

这不是普通的EMA策略,而是双路径精准狙击系统

别再用单一EMA金叉了。这套MNO双步策略把趋势交易拆解成两条完全不同的路径:MOU突破路径和KAKU回调路径。回测数据显示,双路径设计比传统单一信号策略胜率提升30%以上。

核心逻辑很直接:5/13/26三重EMA黄金排列确认趋势方向,然后根据市场状态选择不同的入场时机。不是所有突破都值得追,也不是所有回调都能抄底。

MOU突破路径:大成交量配合MACD零轴附近金叉

MOU路径分两种情况。第一种是经典的阻力突破后回调进场,要求回调幅度在5%-15%之间,太浅说明突破无力,太深说明假突破。第二种是直接突破进场,但条件更苛刻。

突破确认需要收盘价超过前期阻力位0.3%以上,同时K线实体要比过去20周期平均实体大20%以上。这个设计过滤掉了90%的假突破信号。

成交量倍数设置在1.3-3.0倍之间。低于1.3倍说明突破无力,高于3.0倍往往是消息面刺激,后续乏力概率大。

KAKU回调路径:8个基础条件+3个终极确认

KAKU是严格版本,需要满足8个基础条件才能进入候选池。然后还要通过3个终极确认:针脚K线形态、MACD在零轴上方金叉、强势成交量(1.5倍以上)。

这个设计思路很清楚:只在最强势的趋势中寻找最安全的回调买点。历史回测显示KAKU信号胜率超过75%,但出现频率比MOU低60%。

针脚K线的判断标准是下影线长度≥实体的2倍,且收盘价≥开盘价。这种形态在强势回调中成功率最高。

风控设计:2%止盈1%止损,最大持仓30个周期

止盈止损比例2:1看起来保守,但配合30个周期强制平仓,实际上是在控制时间成本。数据显示超过30个周期的持仓,即使最终盈利,年化收益率也会显著下降。

这个策略最大的风险是震荡市场。当价格在EMA26附近反复震荡时,会产生大量假信号。建议在明确的趋势市场中使用,避开财报季和重大事件前后。

参数调优建议:根据标的波动率调整成交量倍数

对于高波动率标的(如成长股),建议将成交量倍数下调至1.2-2.5倍。对于低波动率标的(如大盘蓝筹),可以上调至1.5-3.5倍。

MACD零轴阈值0.2是针对日线级别优化的,如果用于4小时或1小时级别,建议调整至0.1或0.05。

回调幅度5%-15%同样需要根据标的特性调整。高贝塔标的可以放宽至3%-20%,低贝塔标的收紧至4%-12%。

实战应用:优先KAKU信号,MOU作为补充

如果同时出现KAKU和MOU信号,优先选择KAKU。如果只想要最高质量的信号,可以设置为”仅KAKU模式”,预期信号数量会减少但质量更高。

这个策略不适合频繁交易者,平均每月可能只有2-3个高质量信号。但每个信号的风险调整后收益率明显优于市场平均水平。

记住:历史回测不代表未来收益,任何策略都存在连续亏损的可能。严格执行止损,控制单笔仓位不超过总资金的10%。

策略源码
/*backtest
start: 2024-12-17 00:00:00
end: 2025-12-15 08:00:00
period: 1h
basePeriod: 1h
exchanges: [{"eid":"Futures_Binance","currency":"ETH_USDT","balance":500000}]
*/

//@version=5
strategy("MNO_2Step_Strategy_MOU_KAKU (Publish-Clear)", overlay=true, pyramiding=0,
     max_labels_count=500, max_lines_count=500,
     initial_capital=100000,
     default_qty_type=strategy.percent_of_equity, default_qty_value=10)

// =========================
// Inputs
// =========================
emaSLen      = input.int(5,  "EMA Short (5)")
emaMLen      = input.int(13, "EMA Mid (13)")
emaLLen      = input.int(26, "EMA Long (26)")

macdFast     = input.int(12, "MACD Fast")
macdSlow     = input.int(26, "MACD Slow")
macdSignal   = input.int(9,  "MACD Signal")
macdZeroTh   = input.float(0.2, "MOU: MACD near-zero threshold", step=0.05)

volLookback  = input.int(5,  "Volume MA days", minval=1)
volMinRatio  = input.float(1.3, "MOU: Volume ratio min", step=0.1)
volStrong    = input.float(1.5, "Strong volume ratio (Breakout/KAKU)", step=0.1)
volMaxRatio  = input.float(3.0, "Volume ratio max (filter)", step=0.1)

wickBodyMult = input.float(2.0, "Pinbar: lowerWick >= body*x", step=0.1)

pivotLen     = input.int(20, "Resistance lookback", minval=5)
pullMinPct   = input.float(5.0,  "Pullback min (%)", step=0.1)
pullMaxPct   = input.float(15.0, "Pullback max (%)", step=0.1)
breakLookbackBars = input.int(5, "Pullback route: valid bars after break", minval=1)

// --- Breakout route 
useBreakoutRoute     = input.bool(true, "Enable MOU Breakout Route (no pullback)")
breakConfirmPct      = input.float(0.3, "Break confirm: close > R*(1+%)", step=0.1)
bigBodyLookback      = input.int(20, "Break candle body MA length", minval=5)
bigBodyMult          = input.float(1.2, "Break candle: body >= MA*mult", step=0.1)
requireCloseNearHigh = input.bool(true, "Break candle: close near high")
closeNearHighPct     = input.float(25.0, "Close near high threshold (% of range)", step=1.0)
allowMACDAboveZeroInstead = input.bool(true, "Breakout route: allow MACD GC above zero instead")


showEMA        = input.bool(true,  "Plot EMAs")
showMouLabels  = input.bool(true,  "Show MOU/MOU-B labels")
showKakuLabels = input.bool(true,  "Show KAKU labels")
showDebugTbl   = input.bool(true,  "Show debug table (last bar)")
showStatusLbl  = input.bool(true,  "Show status label (last bar always)")

locChoice = input.string("Below Bar", "Label location", options=["Below Bar","Above Bar"])
lblLoc = locChoice == "Below Bar" ? location.belowbar : location.abovebar

// =========================

// =========================
enableTPSL   = input.bool(true, "Enable TP/SL")
tpPct        = input.float(2.0, "Take Profit (%)", step=0.1, minval=0.1) // ←投稿クリア向けに近め
slPct        = input.float(1.0, "Stop Loss (%)",  step=0.1, minval=0.1) // ←投稿クリア向けに近め
maxHoldBars  = input.int(30, "Max bars in trade (force close)", minval=1)

entryMode = input.string("MOU or KAKU", "Entry trigger", options=["KAKU only","MOU or KAKU"])


publishAssist = input.bool(true, "Publish Assist (safety entry if 0 trades)")

// =========================
// EMA
// =========================
emaS = ta.ema(close, emaSLen)
emaM = ta.ema(close, emaMLen)
emaL = ta.ema(close, emaLLen)

plot(showEMA ? emaS : na, color=color.new(color.yellow, 0), title="EMA 5")
plot(showEMA ? emaM : na, color=color.new(color.blue,   0), title="EMA 13")
plot(showEMA ? emaL : na, color=color.new(color.orange, 0), title="EMA 26")

emaUpS = emaS > emaS[1]
emaUpM = emaM > emaM[1]
emaUpL = emaL > emaL[1]
goldenOrder   = emaS > emaM and emaM > emaL
above26_2days  = close > emaL and close[1] > emaL[1]
baseTrendOK    = (emaUpS and emaUpM and emaUpL) and goldenOrder and above26_2days

// =========================
// MACD
// =========================
[macdLine, macdSig, macdHist] = ta.macd(close, macdFast, macdSlow, macdSignal)

macdGC          = ta.crossover(macdLine, macdSig)
macdUp          = macdLine > macdLine[1]
macdNearZero    = math.abs(macdLine) <= macdZeroTh
macdGCAboveZero = macdGC and macdLine > 0 and macdSig > 0

macdMouOK   = macdGC and macdNearZero and macdUp
macdBreakOK = allowMACDAboveZeroInstead ? (macdMouOK or macdGCAboveZero) : macdMouOK

// =========================
// Volume
// =========================
volMA    = ta.sma(volume, volLookback)
volRatio = volMA > 0 ? (volume / volMA) : na

volumeMouOK    = volRatio >= volMinRatio and volRatio <= volMaxRatio
volumeStrongOK = volRatio >= volStrong   and volRatio <= volMaxRatio

// =========================
// Candle patterns
// =========================
body      = math.abs(close - open)
upperWick = high - math.max(open, close)
lowerWick = math.min(open, close) - low

pinbar = (lowerWick >= wickBodyMult * body) and (lowerWick > upperWick) and (close >= open)
bullEngulf = close > open and close[1] < open[1] and close >= open[1] and open <= close[1]
bigBull = close > open and open < emaM and close > emaS and (body > ta.sma(body, 20))
candleOK = pinbar or bullEngulf or bigBull

// =========================
// Resistance / Pullback route
// =========================
res = ta.highest(high, pivotLen)

pullbackPct = res > 0 ? (res - close) / res * 100.0 : na
pullbackOK  = pullbackPct >= pullMinPct and pullbackPct <= pullMaxPct

brokeRes       = ta.crossover(close, res[1])
barsSinceBreak = ta.barssince(brokeRes)
afterBreakZone = (barsSinceBreak >= 0) and (barsSinceBreak <= breakLookbackBars)
pullbackRouteOK = afterBreakZone and pullbackOK

// =========================
// Breakout route 
// =========================
breakConfirm = close > res[1] * (1.0 + breakConfirmPct / 100.0)
bullBreak    = close > open
bodyMA    = ta.sma(body, bigBodyLookback)
bigBodyOK = bodyMA > 0 ? (body >= bodyMA * bigBodyMult) : false
rng = math.max(high - low, syminfo.mintick)
closeNearHighOK = not requireCloseNearHigh ? true : ((high - close) / rng * 100.0 <= closeNearHighPct)

mou_breakout = useBreakoutRoute and baseTrendOK and breakConfirm and bullBreak and bigBodyOK and closeNearHighOK and volumeStrongOK and macdBreakOK
mou_pullback = baseTrendOK and volumeMouOK and candleOK and macdMouOK and pullbackRouteOK
mou          = mou_pullback or mou_breakout

// =========================
// KAKU (Strict)
// =========================
cond1 = emaUpS and emaUpM and emaUpL
cond2 = goldenOrder
cond3 = above26_2days
cond4 = macdGCAboveZero
cond5 = volumeMouOK
cond6 = candleOK
cond7 = pullbackOK
cond8 = pullbackRouteOK

all8_strict = cond1 and cond2 and cond3 and cond4 and cond5 and cond6 and cond7 and cond8
final3      = pinbar and macdGCAboveZero and volumeStrongOK
kaku        = all8_strict and final3

// =========================
// Entry (strategy)
// =========================
entrySignal = entryMode == "KAKU only" ? kaku : (mou or kaku)
canEnter    = strategy.position_size == 0

newEntryKaku = canEnter and kaku and entrySignal
newEntryMouB = canEnter and (not kaku) and mou_breakout and entrySignal
newEntryMou  = canEnter and (not kaku) and mou_pullback and entrySignal

// --- Publish Assist
assistFast = ta.ema(close, 5)
assistSlow = ta.ema(close, 20)
assistEntry = publishAssist and strategy.closedtrades == 0 and canEnter and ta.crossover(assistFast, assistSlow)


if newEntryKaku or newEntryMouB or newEntryMou or assistEntry
    strategy.entry("LONG", strategy.long)


if showMouLabels and newEntryMou
    label.new(bar_index, low, "猛(IN)", style=label.style_label_up, color=color.new(color.lime, 0), textcolor=color.black)
if showMouLabels and newEntryMouB
    label.new(bar_index, low, "猛B(IN)", style=label.style_label_up, color=color.new(color.lime, 0), textcolor=color.black)
if showKakuLabels and newEntryKaku
    label.new(bar_index, low, "確(IN)", style=label.style_label_up, color=color.new(color.yellow, 0), textcolor=color.black)
if assistEntry
    label.new(bar_index, low, "ASSIST(IN)", style=label.style_label_up, color=color.new(color.aqua, 0), textcolor=color.black)

inPos = strategy.position_size > 0
tpPx  = inPos ? strategy.position_avg_price * (1.0 + tpPct/100.0) : na
slPx  = inPos ? strategy.position_avg_price * (1.0 - slPct/100.0) : na

if enableTPSL
    strategy.exit("TP/SL", from_entry="LONG", limit=tpPx, stop=slPx)


var int entryBar = na
if strategy.position_size > 0 and strategy.position_size[1] == 0
    entryBar := bar_index
if strategy.position_size == 0
    entryBar := na

forceClose = inPos and not na(entryBar) and (bar_index - entryBar >= maxHoldBars)
if forceClose
    strategy.close("LONG")

closedThisBar = (strategy.position_size[1] > 0) and (strategy.position_size == 0)

avgPrev = strategy.position_avg_price[1]
tpPrev  = avgPrev * (1.0 + tpPct/100.0)
slPrev  = avgPrev * (1.0 - slPct/100.0)

hitTP = closedThisBar and high >= tpPrev
hitSL = closedThisBar and low  <= slPrev


if hitTP
    label.new(bar_index, high, "利確", style=label.style_label_down, color=color.new(color.lime, 0), textcolor=color.black)
else if hitSL
    label.new(bar_index, low, "損切", style=label.style_label_up, color=color.new(color.red, 0), textcolor=color.white)
else if closedThisBar and forceClose[1]
    label.new(bar_index, close, "時間決済", style=label.style_label_left, color=color.new(color.gray, 0), textcolor=color.white)

// =========================
// Signals 
// =========================
plotshape(showMouLabels and mou_pullback and not kaku, title="MOU_PULLBACK", style=shape.labelup, text="猛",
     color=color.new(color.lime, 0), textcolor=color.black, location=lblLoc, size=size.tiny)

plotshape(showMouLabels and mou_breakout and not kaku, title="MOU_BREAKOUT", style=shape.labelup, text="猛B",
     color=color.new(color.lime, 0), textcolor=color.black, location=lblLoc, size=size.tiny)

plotshape(showKakuLabels and kaku, title="KAKU", style=shape.labelup, text="確",
     color=color.new(color.yellow, 0), textcolor=color.black, location=lblLoc, size=size.small)

// =========================
// Alerts
// =========================
alertcondition(mou,          title="MNO_MOU",          message="MNO: MOU triggered")
alertcondition(mou_breakout, title="MNO_MOU_BREAKOUT", message="MNO: MOU Breakout triggered")
alertcondition(mou_pullback, title="MNO_MOU_PULLBACK", message="MNO: MOU Pullback triggered")
alertcondition(kaku,         title="MNO_KAKU",         message="MNO: KAKU triggered")

alertcondition(assistEntry,  title="MNO_ASSIST_ENTRY", message="MNO: ASSIST ENTRY (publish safety)")
相关推荐