Strategies
指数移动平均交叉智能定投链式止损跟踪系统-EMA-Crossover-Smart-DCA-with-Dual-Trailing-Stop-System
ianzeng123 · PineScript · Risk and Position Sizing · 2025-04-16 15:30:15
Sizing
—
Risk control
Exit
—
Context terms
No English write-up in the source (no author description).
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)