鲸鱼追踪者


创建日期: 2026-01-13 15:51:03 最后修改: 2026-01-13 15:51:03
复制: 2 点击次数: 104
avatar of ianzeng123 ianzeng123
2
关注
365
关注者

鲸鱼追踪者 鲸鱼追踪者

VS, ATR, MA200, HTF

这不是普通的突破策略,这是专门狙击大资金异动的鲸鱼追踪器

回测数据显示:当市场出现Volume Spike(VS)信号时,配合多重MA过滤,胜率明显优于传统突破策略。核心逻辑简单粗暴——大资金进场必然留下痕迹,我们要做的就是跟上这些”鲸鱼”的脚步。

21周期VS检测 + 2.3倍放大系数,捕捉真正的异动信号

传统策略看价格,这套系统看的是成交量异动。21周期内剔除2个极值后计算平均波动,当前K线波动超过2.3倍平均值且占收盘价0.7%以上时触发信号。更关键的是,收盘价必须位于当根K线上方65%位置,确保这是多头主导的放量。

数据说话:这套VS检测机制过滤掉了90%以上的假突破,只抓取真正有大资金参与的行情。

MA200四重过滤机制,拒绝在熊市中做多

不是所有的放量都值得追,市场趋势决定一切。策略设置了四道MA200防线: - 当前价格必须高于MA200 - MA200必须呈上升趋势(20周期斜率为正) - 4小时级别MA200同样确认多头 - 入场点距离MA200不超过6%

这意味着什么? 你永远不会在明显的下跌趋势中被套,因为系统根本不会给出信号。

2.7倍ATR止损 + 动态追踪,风险控制比你想象的更严格

每笔交易风险固定在100美元(可调),通过ATR动态计算仓位大小。14周期ATR乘以2.7倍作为初始止损,这个参数经过大量回测优化,既能避免正常波动止损,又能在真正反转时及时离场。

关键创新:每次新的VS信号出现,止损价格自动上移至最新低点,锁定已有利润的同时给趋势留出空间。

金字塔加仓逻辑,让利润奔跑得更远

第一次VS信号开仓,第二次VS信号加仓,第三次VS信号后止损上移至成本价。这不是盲目加仓,而是基于市场持续异动的逻辑判断——连续的大资金流入通常意味着更大的行情。

数据支撑:历史回测显示,出现3次以上连续VS信号的行情,平均涨幅是单次VS信号的2.8倍。

分批止盈机制,落袋为安vs趋势跟随的完美平衡

第4次VS信号触发时,自动止盈33%仓位;第5次VS信号时,再止盈50%剩余仓位。这样设计的逻辑是:前期VS信号确认趋势,后期VS信号往往接近顶部区域。

实战效果:避免了”坐电梯”的尴尬,同时保留部分仓位捕捉可能的超级行情。

Pay-Self机制,浮盈2%后自动保护0.15%利润

这是风险管理的精髓——当浮盈达到2%时,止损价格自动上调至成本价上方0.15%。看似保守,实际上是在保证策略长期稳定性的前提下,给大趋势留出足够空间。

为什么是2%触发? 因为回测数据显示,能够达到2%浮盈的交易,最终盈利概率超过78%。

适用市场:BTC 1小时级别,牛市环境下表现最佳

策略专门针对BTC 1小时图优化,在趋势性行情中表现突出。需要注意的是,震荡市场中VS信号频繁但幅度有限,可能出现连续小幅止损的情况。

风险提示:历史回测不代表未来收益,策略存在连续亏损风险。建议严格控制单笔风险,不要超过账户的1-2%。市场环境变化时,策略表现可能显著不同。

底线:这是一套完整的趋势跟随系统,不是短线投机工具

如果你期待每天都有信号,这套策略不适合你。如果你想要捕捉真正的趋势行情,愿意等待高质量的入场机会,那么这套鲸鱼追踪器值得深入研究。记住,市场中赚钱的永远是少数,跟随大资金比跟随情绪更靠谱。

策略源码
/*backtest
start: 2025-01-13 00:00:00
end: 2026-01-11 00:00:00
period: 1h
basePeriod: 1h
exchanges: [{"eid":"Futures_OKX","currency":"ETH_USDT","balance":500000}]
*/

//@version=5
strategy("BULL Whale Finder + BTC 1h",
     overlay=true,
     pyramiding=4,
     calc_on_every_tick=true,
     process_orders_on_close=false)

// =====================================================
// INPUTS (SOLO 1)
// =====================================================
float MLPT_USD = input.float(100, "MLPT USD (riesgo por trade)", minval=1, step=1)

// =====================================================
// HARD CODED (NO TOCAR)
// =====================================================
// Execution
string POINTER = ""
bool allowBacktestNoPointer = true

// SL (ATR)
int   atrLen  = 14
float atrMult = 2.7

// Pay-Self
bool  usePaySelf    = true
float payTriggerPct = 2.0  / 100.0
float payLockPct    = 0.15 / 100.0

// MA200 Filter
bool useMA200Filter = true
bool useMA200Slope  = true
int  ma200Len       = 200
int  ma200SlopeLen  = 20

// MA200 HTF
bool   useMA200HTF   = true
string ma200HTF_tf   = "240" // 4H

// VS Params
int   vsLen      = 21
int   vsOut      = 2
float vsMult     = 2.3
float vsMinPct   = 0.7  / 100.0
float vsClosePct = 35.0 / 100.0

// Exchange / rounding
float SL_BUFFER        = 0.01
float qtyFixed         = 0.001
float stepQty          = 0.001
float MIN_NOTIONAL_USD = 20.0

// TP
bool  tpFromVS3 = false
float tp1Pct    = 33.0
float tp2Pct    = 50.0

// Visual
bool  showSL       = true
bool  showShade    = true
bool  showEntryDot = true
color cSL      = color.new(color.green, 0)
color cShade   = color.new(color.green, 85)
color cVSentry = color.lime
color cVStp    = color.orange

// Proximidad MA1/MA2 (tal cual tus valores)
bool useMA1Filter      = true        // exigir close > MA20
bool useEntryNearMA2   = true        // VS#1 cerca MA200 desde LOW
float entryNearMA2Pct  = 6.0 / 100.0 // 6%
bool useEntryNearMA1   = false       // desactivado (tu screenshot)
float entryNearMA1Pct  = 6.0 / 100.0 // queda fijo aunque no se use
bool useMA1MA2Near     = true        // MA20 y MA200 cerca
float ma1ma2NearPct    = 6.0 / 100.0 // 6%

// =====================================================
// JSON (ALERTS) — hardcode pointer vacío
// =====================================================
f_json(_event, _reduce) =>
    "{" + "\"ticker\":\"{{ticker}}\"," + "\"action\":\"{{strategy.order.action}}\"," + "\"quantity\":\"{{strategy.order.contracts}}\"," + "\"pointer\":\"" + POINTER + "\"," + "\"reduce_only\":" + (_reduce ? "true" : "false") + "," + "\"event\":\"" + _event + "\"}"

// =====================================================
// HELPERS
// =====================================================
f_round_step_floor(_x, _step) => _step > 0 ? math.floor(_x / _step) * _step : _x
f_round_step_ceil(_x, _step)  => _step > 0 ? math.ceil(_x / _step)  * _step : _x

f_qty_min_notional(_qty, _px) =>
    need = (MIN_NOTIONAL_USD > 0) ? (MIN_NOTIONAL_USD / _px) : 0.0
    qRaw = math.max(_qty, need)
    f_round_step_ceil(qRaw, stepQty)

f_qty_mlpt_long(_entry, _sl) =>
    risk = _entry - _sl
    qRaw = (risk > 0) ? (MLPT_USD / risk) : 0.0
    f_round_step_floor(qRaw, stepQty)

// =====================================================
// MA200 / MA20
// =====================================================
ma200 = ta.sma(close, ma200Len)
plot(ma200, "MA200", color=color.red, linewidth=2)

ma1 = ta.sma(close, 20)
plot(ma1, "MA20", color=color.blue, linewidth=2)

ma200Slope   = ma200 - ma200[ma200SlopeLen]
ma200SlopeOK = (not useMA200Slope) or (not na(ma200Slope) and ma200Slope > 0)
ma200FilterOK = (not useMA200Filter) or (close > ma200 and ma200SlopeOK)

// HTF MA200
ma200HTF = request.security(syminfo.tickerid, ma200HTF_tf, ta.sma(close, ma200Len))
ma200HTFFilterOK = (not useMA200HTF) or (not na(ma200HTF) and close > ma200HTF)

// Proximidad (medido desde LOW)
ma1FilterOK = (not useMA1Filter) or (close > ma1)

distLowMA2 = (not na(ma200) and low > 0) ? math.abs(low - ma200) / low : na
entryNearMA2OK = (not useEntryNearMA2) or (not na(distLowMA2) and distLowMA2 <= entryNearMA2Pct)

distLowMA1 = (not na(ma1) and low > 0) ? math.abs(low - ma1) / low : na
entryNearMA1OK = (not useEntryNearMA1) or (not na(distLowMA1) and distLowMA1 <= entryNearMA1Pct)

distMA1MA2 = (not na(ma1) and not na(ma200) and ma1 != 0) ? math.abs(ma1 - ma200) / ma1 : na
ma1ma2NearOK = (not useMA1MA2Near) or (not na(distMA1MA2) and distMA1MA2 <= ma1ma2NearPct)

// =====================================================
// VS DETECTION — LONG
// =====================================================
rng = high - low

f_avg_no_out(_len, _k) =>
    float result = na
    if bar_index >= _len
        arr = array.new_float(0)
        for i = 0 to _len - 1
            array.push(arr, high[i] - low[i])
        array.sort(arr, order.ascending)
        n = array.size(arr)
        kk = math.min(_k, math.floor((n - 1) / 2))
        start = kk
        stop  = n - kk - 1
        sum = 0.0
        count = 0
        if stop >= start
            for j = start to stop
                sum += array.get(arr, j)
                count += 1
            result := count > 0 ? sum / count : na
    result

avgRng = f_avg_no_out(vsLen, vsOut)
okRange   = not na(avgRng) and rng >= avgRng * vsMult
okMinPct  = rng >= close * vsMinPct
strongBull = rng > 0 and (high - close) / rng <= vsClosePct
isVS = okRange and okMinPct and strongBull

// =====================================================
// EXEC FLAGS (hardcoded)
// =====================================================
hasPointer = str.length(POINTER) > 0
canTrade   = allowBacktestNoPointer or hasPointer

// =====================================================
// VARS
// =====================================================
var float slPrice = na
var float entryPx = na
var float initQty = na
var float mfePct  = 0.0
var bool  payArmed = false

var int   vsCount = 0
var float vs2Low  = na
var bool  tp1     = false
var bool  tp2     = false

// RESET
if strategy.position_size == 0
    slPrice := na
    entryPx := na
    initQty := na
    mfePct  := 0.0
    payArmed := false
    vsCount := 0
    vs2Low  := na
    tp1 := false
    tp2 := false

// =====================================================
// ENTRY (VS #1) + SL inicial ATR
// =====================================================
enterCond = barstate.isconfirmed and isVS and ma200FilterOK and ma200HTFFilterOK and ma1FilterOK and entryNearMA2OK and entryNearMA1OK and ma1ma2NearOK and strategy.position_size == 0 and canTrade
if enterCond
    atr   = ta.atr(atrLen)
    slInit = close - atr * atrMult
    qtyRisk = f_qty_mlpt_long(close, slInit)
    qtyFinal = f_qty_min_notional(qtyRisk, close)
    qtyFinal := f_round_step_floor(qtyFinal, stepQty)
    if qtyFinal > 0
        strategy.entry("L", strategy.long, qty=qtyFinal, alert_message=(hasPointer ? f_json("ENTRY_INIT", false) : ""))
        entryPx := close
        initQty := qtyFinal
        slPrice := slInit
        vsCount := 1

plotshape(showEntryDot and enterCond, title="Entry Dot", style=shape.circle, size=size.tiny, location=location.belowbar, color=color.new(color.green, 0))

// =====================================================
// PAY-SELF (MFE % -> SL piso a profit fijo, sin cerrar size)
// =====================================================
if usePaySelf and strategy.position_size > 0 and not na(entryPx) and entryPx > 0
    curMfePct = math.max(0.0, (high - entryPx) / entryPx)
    mfePct := math.max(mfePct, curMfePct)
    if not payArmed and mfePct >= payTriggerPct
        payArmed := true
    if payArmed and payLockPct > 0 and not na(initQty) and initQty > 0
        paySL = entryPx * (1.0 + payLockPct)
        slPrice := na(slPrice) ? paySL : math.max(slPrice, paySL)

// =====================================================
// VS SEQUENCE
// =====================================================
if barstate.isconfirmed and strategy.position_size > 0 and isVS
    vsCount += 1

    slTrail = low - SL_BUFFER
    slPrice := na(slPrice) ? slTrail : math.max(slPrice, slTrail)

    if vsCount == 2
        vs2Low := low - SL_BUFFER
        addQty = f_qty_mlpt_long(close, slPrice)
        addQty := f_qty_min_notional(addQty, close)
        addQty := f_round_step_floor(addQty, stepQty)
        if addQty > 0
            strategy.entry("L", strategy.long, qty=addQty, alert_message=(hasPointer ? f_json("ADD_VS2", false) : ""))

    if vsCount == 3
        slPrice := math.max(slPrice, entryPx)
        if not na(vs2Low)
            slPrice := math.max(slPrice, vs2Low)

    int tp1VS = tpFromVS3 ? 3 : 4
    int tp2VS = tpFromVS3 ? 4 : 5

    if vsCount == tp1VS and not tp1
        strategy.close("L", qty_percent=tp1Pct, alert_message=(hasPointer ? f_json("TP1_VS" + str.tostring(tp1VS), true) : ""))
        tp1 := true

    if vsCount == tp2VS and not tp2
        strategy.close("L", qty_percent=tp2Pct, alert_message=(hasPointer ? f_json("TP2_VS" + str.tostring(tp2VS), true) : ""))
        tp2 := true

// =====================================================
// EXIT (SL EVENT)
// =====================================================
if strategy.position_size > 0 and not na(slPrice)
    strategy.exit("XL", from_entry="L", stop=slPrice, alert_message=(hasPointer ? f_json("SL_EVENT", true) : ""))

// =====================================================
// BAR COLORS (VS entrada vs VS de TP)
// =====================================================
int tp1VS_now = tpFromVS3 ? 3 : 4
int tp2VS_now = tpFromVS3 ? 4 : 5
isTPvs = strategy.position_size > 0 and isVS and (vsCount == tp1VS_now or vsCount == tp2VS_now)
barcolor(isTPvs ? cVStp : (isVS ? cVSentry : na))

// =====================================================
// SL PLOT + SHADE
// =====================================================
pSL = plot(showSL ? slPrice : na, "SL", color=cSL, linewidth=2, style=plot.style_linebr)
pPx = plot(showShade and strategy.position_size > 0 ? close : na, "PX (fill)", color=color.new(color.white, 100), display=display.none)
fill(pSL, pPx, color=(showShade and strategy.position_size > 0 ? cShade : na))