Strategies KB.ZH
Strategies

指数移动平均交叉智能定投链式止损跟踪系统-EMA-Crossover-Smart-DCA-with-Dual-Trailing-Stop-System

ianzeng123 · PineScript · Risk and Position Sizing · 2025-04-16 15:30:15

All strategies →

No English write-up in the source (no author description).

Read the Chinese version →

Source code (206 lines)
/*backtest
start: 2024-04-16 00:00:00
end: 2025-04-15 00:00:00
period: 1d
basePeriod: 1d
exchanges: [{"eid":"Futures_Binance","currency":"BTC_USDT"}]
*/

//@version=5
strategy(
     title="BONK/USD (1H) - $4k DCA + Dual Trailing + Date Filter", // Updated Title
     overlay=true,
     initial_capital=4000,
     currency=currency.USD,
     default_qty_type=strategy.fixed,
     default_qty_value=0, // Quantity calculated dynamically based on USD value
     commission_type=strategy.commission.percent, // Example: Add commission settings
     commission_value=0.1 // Example: 0.1% commission
     )

// 1) USER INPUTS (Defaults adjusted for 1H timeframe - REQUIRES BACKTESTING/TUNING)

// --- Date Filter ---
useDateFilter  = input.bool(true, title="Enable Date Filter?")


// --- Trend ---
fastMALen = input.int(9, title="Fast EMA Length (Default for 1H)")
slowMALen = input.int(21, title="Slow EMA Length (Default for 1H)")

// --- Trailing Stops ---
trailStopPerc   = input.float(8.0, title="Standard Trailing Stop (%) (Default for 1H)", minval=0.1) / 100
lockInThreshold = input.float(2.5,  title="Profit Lock-In Trigger (%) (Default for 1H)", minval=0.1) / 100
lockInTrailPct  = input.float(1.5,  title="Lock-In Trail (%) after Trigger (Default for 1H)", minval=0.1) / 100

// --- Safety Orders (SO) ---
useATRSpacing     = input.bool(true, title="Use ATR-Based Spacing?")
atrLength         = input.int(14,   title="ATR Length", minval=1)
atrSo1Multiplier  = input.float(1.2, title="ATR SO1 Multiplier (Default for 1H)", minval=0.1)
atrSo2Multiplier  = input.float(2.5, title="ATR SO2 Multiplier (Default for 1H)", minval=0.1)

// --- Fallback SO Spacing (if not using ATR) ---
fallbackSo1Perc = input.float(4.0,  title="Fallback SO1 Drop (%) (Default for 1H)", minval=0.1) / 100
fallbackSo2Perc = input.float(8.0, title="Fallback SO2 Drop (%) (Default for 1H)", minval=0.1) / 100

// --- Entry Cooldown ---
cooldownBars = input.int(4, "Cooldown Bars After Base Entry (Default for 1H)", minval=0)

// --- Order Sizes in USD ---
baseUsd = input.float(1000.0, title="Base Order Size (USD)", minval=1.0)
so1Usd  = input.float(1250.0, title="Safety Order 1 Size (USD)", minval=1.0)
so2Usd  = input.float(1750.0, title="Safety Order 2 Size (USD)", minval=1.0)

// 2) CALCULATIONS

// --- Trend & Reversal Detection ---
fastMA    = ta.ema(close, fastMALen)
slowMA    = ta.ema(close, slowMALen)
trendUp   = ta.crossover(fastMA, slowMA)
trendDown = ta.crossunder(fastMA, slowMA)

// --- ATR Value ---
atrValue = ta.atr(atrLength)

// --- Date Range Check ---

// 3) BASE ENTRY LOGIC

// Base Buy Signal: EMA crossover
baseBuySignal = trendUp

var int   lastBuyBar     = na // Tracks the bar index of the last base entry
inCooldown = not na(lastBuyBar) and (bar_index - lastBuyBar < cooldownBars)

var float baseEntryPrice = na // Stores the price of the initial base entry for SO calculations

// --- Execute Base Entry ---
// Added 'inDateRange' to the condition
if baseBuySignal and strategy.position_size == 0 and not inCooldown
    baseQty = baseUsd / close // Calculate quantity based on USD
    strategy.entry("Base Order", strategy.long, qty=baseQty, comment="Base Entry")
    baseEntryPrice := close
    lastBuyBar     := bar_index

// 4) SAFETY ORDERS LOGIC

// --- Calculate SO Trigger Prices ---
float so1TriggerPrice = na
float so2TriggerPrice = na

if not na(baseEntryPrice) // Only calculate if a base order has been placed
    so1TriggerPrice := useATRSpacing ?
         (baseEntryPrice - atrValue * atrSo1Multiplier) :
         (baseEntryPrice * (1 - fallbackSo1Perc))

    so2TriggerPrice := useATRSpacing ?
         (baseEntryPrice - atrValue * atrSo2Multiplier) :
         (baseEntryPrice * (1 - fallbackSo2Perc))

// --- Conditions for SO Execution ---
// Added 'inDateRange' check
// Ensure base order exists, price trigger hit, and the specific SO hasn't filled yet
bool so1Condition = not na(baseEntryPrice) and strategy.position_size > 0 and close <= so1TriggerPrice and strategy.opentrades == 1
bool so2Condition = not na(baseEntryPrice) and strategy.position_size > 0 and close <= so2TriggerPrice and strategy.opentrades == 2

// --- Execute SO1 ---
if so1Condition
    so1Qty = so1Usd / close // Calculate quantity based on USD
    strategy.entry("Safety Order 1", strategy.long, qty=so1Qty, comment="SO1")

// --- Execute SO2 ---
if so2Condition
    so2Qty = so2Usd / close // Calculate quantity based on USD
    strategy.entry("Safety Order 2", strategy.long, qty=so2Qty, comment="SO2")

// 5) AVERAGE ENTRY PRICE

// Use the built-in variable for the average price of the open position
avgEntryPrice = strategy.position_avg_price

// 6) DUAL TRAILING STOP LOGIC

// Variables to track trailing stop levels and states
var float highestSinceEntry = na
var float trailStopPrice    = na
var bool  stopHitNormal     = false

var bool  lockInTriggered = false
var float lockInPeak      = na
var float lockInStopPrice = na
var bool  stopHitLockIn   = false

// --- Update Trailing Logic when in a Position ---
if strategy.position_size > 0
    // --- Standard Trail ---
    highestSinceEntry := na(highestSinceEntry) ? close : math.max(highestSinceEntry, close)
    trailStopPrice    := highestSinceEntry * (1 - trailStopPerc)
    stopHitNormal     := close < trailStopPrice

    // --- Lock-In Trail ---
    if not lockInTriggered and close >= avgEntryPrice * (1 + lockInThreshold)
        lockInTriggered := true
        lockInPeak      := close

    if lockInTriggered
        lockInPeak      := math.max(lockInPeak, close)
        lockInStopPrice := lockInPeak * (1 - lockInTrailPct)
        stopHitLockIn   := close < lockInStopPrice
    else
        stopHitLockIn   := false
        lockInStopPrice := na

// --- Reset Variables when Flat ---
else
    highestSinceEntry := na
    trailStopPrice    := na
    stopHitNormal     := false

    lockInTriggered   := false
    lockInPeak        := na
    lockInStopPrice   := na
    stopHitLockIn     := false

    baseEntryPrice    := na
    // lastBuyBar is intentionally NOT reset here, cooldown depends on it

// 7) EXIT CONDITIONS

// Added 'inDateRange' check
// Exit if either trailing stop is hit OR if the trend reverses downward, within the active date range
exitCondition = (stopHitNormal or stopHitLockIn or trendDown) and strategy.position_size > 0

if exitCondition
    strategy.close_all(comment="Exit: SL / LockIn / TrendDown")

// 8) ALERT CONDITIONS (Potential 3Commas Integration)
// WARNING: Verify and adapt these JSON message strings for your specific 3Commas bot configuration!
//          The required format ('action', parameters, etc.) can vary.

// Added 'inDateRange[1]' check for Base Alert
alertcondition(baseBuySignal and strategy.position_size[1] == 0 and not inCooldown[1],
     title="Base Buy Alert",
     message='{"action":"start_deal","order":"base"} // Verify/Adapt JSON for your 3Commas bot!')

// Added 'inDateRange' check for SO1 Alert
alertcondition(so1Condition, title="SO1 Alert",
     message='{"action":"add_funds","order":"so1"} // Verify/Adapt JSON for your 3Commas bot!')

// Added 'inDateRange' check for SO2 Alert
alertcondition(so2Condition, title="SO2 Alert",
     message='{"action":"add_funds","order":"so2"} // Verify/Adapt JSON for your 3Commas bot!')

// Added 'inDateRange' check for Exit Alert
alertcondition(exitCondition, title="Exit Alert",
     message='{"action":"close_at_market_price"} // Verify/Adapt JSON for your 3Commas bot!')


// 9) PLOTS & DEBUG TABLE

// --- Plot MAs ---
plot(fastMA, color=color.new(color.green, 0), title="Fast EMA", linewidth=2)
plot(slowMA, color=color.new(color.red, 0),   title="Slow EMA", linewidth=2)

// --- Plot Trailing Stops ---
plot(strategy.position_size > 0 ? trailStopPrice : na, color=color.new(color.orange, 0), title="Standard Trailing Stop", style=plot.style_linebr, linewidth=2)
plot(lockInTriggered ? lockInStopPrice : na, color=color.new(color.fuchsia, 0), title="Lock-In Trailing Stop", style=plot.style_linebr, linewidth=2)