这是一个基于凯特纳通道(Keltner Channel)的灵活交易策略。该策略支持多空双向交易,通过对价格突破通道上下轨的监控来进行交易。策略的核心在于使用移动平均线(MA)构建价格通道,并结合真实波幅(ATR)来动态调整通道宽度,从而在不同市场环境下保持策略的适应性。
策略主要基于以下几个核心原理: 1. 通过EMA或SMA计算价格的中心趋势,形成通道中轨 2. 利用ATR、TR或Range计算波动率,构建通道上下轨 3. 当价格突破上轨时触发做多信号,突破下轨时触发做空信号 4. 采用止损委托单机制进行入场和出场,提高交易执行的可靠性 5. 支持灵活的交易模式选择:仅做多、仅做空或双向交易
该策略是一个设计完善、逻辑清晰的交易系统,通过灵活运用凯特纳通道和多种技术指标,实现了对市场机会的有效捕捉。策略的可定制性强,适合不同风险偏好的交易者使用。通过持续优化和改进,该策略有望在各类市场环境下保持稳定的表现。
/*backtest
start: 2022-02-11 00:00:00
end: 2025-02-08 08:00:00
period: 1d
basePeriod: 1d
exchanges: [{"eid":"Futures_Binance","currency":"BTC_USDT"}]
*/
//@version=6
strategy(title = "Jaakko's Keltner Strategy", overlay = true, initial_capital = 10000, default_qty_type = strategy.percent_of_equity, default_qty_value = 100)
// ──────────────────────────────────────────────────────────────────────────────
// ─── USER INPUTS ─────────────────────────────────────────────────────────────
// ──────────────────────────────────────────────────────────────────────────────
length = input.int(20, minval=1, title="Keltner MA Length")
mult = input.float(2.0, title="Multiplier")
src = input(close, title="Keltner Source")
useEma = input.bool(true, title="Use Exponential MA")
BandsStyle = input.string(title = "Bands Style", defval = "Average True Range", options = ["Average True Range", "True Range", "Range"])
atrLength = input.int(10, title="ATR Length")
// Choose which side(s) to trade
tradeMode = input.string(title = "Trade Mode", defval = "Long Only", options = ["Long Only", "Short Only", "Both"])
// ──────────────────────────────────────────────────────────────────────────────
// ─── KELTNER MA & BANDS ───────────────────────────────────────────────────────
// ──────────────────────────────────────────────────────────────────────────────
f_ma(source, length, emaMode) =>
maSma = ta.sma(source, length)
maEma = ta.ema(source, length)
emaMode ? maEma : maSma
ma = f_ma(src, length, useEma)
rangeMa = BandsStyle == "True Range" ? ta.tr(true) : BandsStyle == "Average True Range" ? ta.atr(atrLength) : ta.rma(high - low, length)
upper = ma + rangeMa * mult
lower = ma - rangeMa * mult
// ──────────────────────────────────────────────────────────────────────────────
// ─── CROSS CONDITIONS ─────────────────────────────────────────────────────────
// ──────────────────────────────────────────────────────────────────────────────
crossUpper = ta.crossover(src, upper) // potential long signal
crossLower = ta.crossunder(src, lower) // potential short signal
// ──────────────────────────────────────────────────────────────────────────────
// ─── PRICE LEVELS FOR STOP ENTRY (LONG) & STOP ENTRY (SHORT) ─────────────────
// ──────────────────────────────────────────────────────────────────────────────
bprice = 0.0
bprice := crossUpper ? high + syminfo.mintick : nz(bprice[1])
sprice = 0.0
sprice := crossLower ? low - syminfo.mintick : nz(sprice[1])
// ──────────────────────────────────────────────────────────────────────────────
// ─── BOOLEAN FLAGS FOR PENDING LONG/SHORT ─────────────────────────────────────
// ──────────────────────────────────────────────────────────────────────────────
crossBcond = false
crossBcond := crossUpper ? true : crossBcond[1]
crossScond = false
crossScond := crossLower ? true : crossScond[1]
// Cancel logic for unfilled orders (same as original)
cancelBcond = crossBcond and (src < ma or high >= bprice)
cancelScond = crossScond and (src > ma or low <= sprice)
// ──────────────────────────────────────────────────────────────────────────────
// ─── LONG SIDE ────────────────────────────────────────────────────────────────
// ──────────────────────────────────────────────────────────────────────────────
if (tradeMode == "Long Only" or tradeMode == "Both") // Only run if mode is long or both
// Cancel unfilled long if invalid
if cancelBcond
strategy.cancel("KltChLE")
// Place long entry
if crossUpper
strategy.entry("KltChLE", strategy.long, stop=bprice, comment="Long Entry")
// If we are also using “Both,” we rely on short side to flatten the long.
// But if “Long Only,” we can exit on crossLower or do nothing.
// Let’s do a "stop exit" if in "Long Only" (like the improved version).
if tradeMode == "Long Only"
// Cancel unfilled exit
if cancelScond
strategy.cancel("KltChLX")
// Place exit if crossLower
if crossLower
strategy.exit("KltChLX", from_entry="KltChLE", stop=sprice, comment="Long Exit")
// ──────────────────────────────────────────────────────────────────────────────
// ─── SHORT SIDE ───────────────────────────────────────────────────────────────
// ──────────────────────────────────────────────────────────────────────────────
if (tradeMode == "Short Only" or tradeMode == "Both") // Only run if mode is short or both
// Cancel unfilled short if invalid
if cancelScond
strategy.cancel("KltChSE")
// Place short entry
if crossLower
strategy.entry("KltChSE", strategy.short, stop=sprice, comment="Short Entry")
// If “Short Only,” we might do a symmetrical exit approach for crossUpper
// Or if "Both," going long automatically flattens the short in a no-hedge account.
// Let's replicate "stop exit" for short side if "Short Only" is chosen:
if tradeMode == "Short Only"
// Cancel unfilled exit
if cancelBcond
strategy.cancel("KltChSX")
// Place exit if crossUpper
if crossUpper
strategy.exit("KltChSX", from_entry="KltChSE", stop=bprice, comment="Short Exit")
// ──────────────────────────────────────────────────────────────────────────────
// ─── OPTIONAL VISUALS ─────────────────────────────────────────────────────────
// ──────────────────────────────────────────────────────────────────────────────
barcolor(strategy.position_size > 0 ? color.green : strategy.position_size < 0 ? color.red : na)
plotshape( strategy.position_size > 0 and strategy.position_size[1] <= 0, title = "BUY", text = '🚀', style = shape.labelup, location = location.belowbar, color = color.green, textcolor = color.white, size = size.small)
plotshape( strategy.position_size <= 0 and strategy.position_size[1] > 0, title = "SELL", text = '☄️', style = shape.labeldown, location = location.abovebar, color = color.red, textcolor = color.white, size = size.small)
plotshape(crossLower, style=shape.triangledown, color=color.red, location=location.abovebar, title="CrossLower Trigger")