
Là một nhà giao dịch định lượng, tôi thường được hỏi rằng: Tại sao những chiến lược hoạt động tốt trong thị trường xu hướng lại bắt đầu rút lui một cách mạnh mẽ khi thị trường bị chấn động?
Câu trả lời rất đơn giản: hầu hết các chiến lược theo dõi xu hướng đều mắc bệnh “trend obsessive” - chúng luôn cố gắng giữ cho giao dịch có tần số cao trong bất kỳ môi trường thị trường nào, nhưng bỏ qua một sự thật cơ bản:Thị trường giao dịch 70% thời gian trong trạng thái dao động ngang。
Một trong những giải pháp thú vị cho vấn đề này chính là “chiến lược trung bình thang xu hướng” mà chúng tôi sẽ phân tích hôm nay:Theo dõi tích cực trong thị trường xu hướng, “thư giãn thanh lịch” trong thị trường biến động。
Các chiến lược trung bình di chuyển truyền thống có một nhược điểm nghiêm trọng: chúng luôn thay đổi. Bất kể thị trường có xu hướng mạnh hay dao động ngang, đường trung bình sẽ liên tục điều chỉnh theo biến động giá, dẫn đến rất nhiều tín hiệu sai.
Và ý tưởng cốt lõi của The Ladder Average là:Để đường trung bình “đóng băng” trong một số điều kiện。
Logic thực hiện cụ thể như sau:
Phát hiện trạng thái xu hướngCác nhà đầu tư khác cũng có thể tham gia vào chương trình này.
Chuyển đổi động:
Một số người dùng đã sử dụng thiết kế này để tạo ra một trang web.Nó cho phép các chiến lược thể hiện “nhân vật” khác nhau trong các môi trường thị trường khác nhauCác nhà nghiên cứu cho biết, trong một số trường hợp, các con rùa có thể bị trượt, nhưng chúng lại có thể bị trượt.
Ngoài cơ chế trung bình bậc thang cơ bản, chiến lược này còn tích hợp một mô-đun “lấy xu hướng” mà tôi cho là sáng tạo nhất:
Cơ chế quay ngược nhanh:
Thiết kế này giải quyết một vấn đề quan trọng của chiến lược truyền thống:Làm thế nào để điều chỉnh vị trí nhanh chóng khi xu hướng thay đổi。
Hãy tưởng tượng một tình huống như thế này: bạn vừa xóa nhiều vị trí đầu vì dừng lỗ, và kết quả là thị trường ngay lập tức có xu hướng giảm mạnh. Chiến lược truyền thống có thể cần phải chờ đợi tín hiệu mới được xác nhận, nhưng hệ thống “làm theo xu hướng” này có thể nhanh chóng thiết lập vị trí đầu trống trong vòng 3 chu kỳ.
Điều đáng học hỏi nhất về chiến lược này làCơ chế quản lý rủi ro khác biệt:
Kiểm soát rủi ro trong thị trường ngang:
Kiểm soát rủi ro trong thị trường xu hướng:
Thiết kế này thể hiện một triết lý giao dịch quan trọng:Các môi trường thị trường khác nhau đòi hỏi các ưu tiên rủi ro khác nhauTrong thị trường ngang, chúng ta nên thận trọng hơn; trong thị trường xu hướng, chúng ta cần cho lợi nhuận nhiều hơn.
Hạn chế di chuyển truyền thống thường quá cơ khí, hoặc quá căng thẳng dẫn đến việc ra đi sớm, hoặc quá chậm chạp không bảo vệ lợi nhuận một cách hiệu quả. Hạn chế di chuyển bậc thang của chiến lược này cung cấp một giải pháp thông minh hơn:
Lập trình thang logic:
Những ưu điểm của thiết kế này là:Nó có thể bảo vệ lợi nhuận của bạn, nhưng nó cũng có thể cho bạn đủ không gian để phát triển xu hướng.。
Dựa trên kinh nghiệm thực tế của tôi, khi sử dụng các chiến lược như vậy cần lưu ý những điều sau đây:
Lạm dụng tham số tối ưu hóaLưu ý: Không quá tối ưu hóa các ngưỡng ADX, số từ 25-30 hoạt động ổn định trên hầu hết các thị trường
Thị trường thích ứngChiến lược này phù hợp hơn với thị trường có độ biến động vừa phải, có thể cần điều chỉnh ATR trong môi trường biến động cực đoan
Quản lý tài chínhGhi chú: Đề xuất không vượt quá 10% tổng số vốn, đặc biệt là khi bật tính năng chụp xu hướng
Bẫy phản hồiCần chú ý đặc biệt đến tác động của điểm trượt và phí xử lý, đặc biệt là giao dịch thường xuyên trong thị trường bất ổn.
Từ quan điểm về sự phát triển của chiến lược định lượng, chiến lược này đại diện cho một hướng tiến hóa quan trọng:Chuyển đổi từ logic đơn sang tự điều chỉnh đa trạng thái。
Chiến lược truyền thống thường cố gắng đối phó với tất cả các tình huống thị trường bằng một bộ logic cố định, trong khi chiến lược này thể hiện sự khôn ngoan của “địa điểm phù hợp”:
Đây là một ý tưởng thiết kế có ý nghĩa quan trọng đối với các nhà phát triển chiến lược:Chúng ta nên làm cho chiến lược có khả năng “nhận thức thị trường”, chứ không phải là một cách mù quáng thực hiện logic cố định.。
Cuối cùng, cần nhấn mạnh rằng không có chiến lược nào là toàn năng. Chiến lược trung bình bậc thang này, mặc dù có vẻ thanh lịch về mặt lý thuyết, vẫn cần được điều chỉnh trong ứng dụng thực tế kết hợp với môi trường thị trường cụ thể và sở thích rủi ro cá nhân.Chiến lược tốt nhất luôn là chiến lược phù hợp nhất với bạn.。
/*backtest
start: 2024-10-09 00:00:00
end: 2025-10-07 08:00:00
period: 1d
basePeriod: 1d
exchanges: [{"eid":"Futures_Binance","currency":"SOL_USDT","balance":500000}]
*/
//@version=5
strategy("Trend Following Ladder Average Strategy", overlay=true, default_qty_type=strategy.percent_of_equity, default_qty_value=10)
// ═══════════════════════════════════════════════════════════════════════════════
// SETTINGS AND PARAMETERS
// ═══════════════════════════════════════════════════════════════════════════════
// Ladder Average Settings
ma_length = input.int(title="Average Period", defval=21, minval=5)
ma_type = input.string(title="Average Type", defval="EMA", options=["SMA", "EMA", "WMA"])
// Trend Strength Settings
adx_length = input.int(title="Trend Strength Period (ADX)", defval=14, minval=5)
trend_threshold = input.float(title="Trend Strength Threshold", defval=25.0, minval=10.0, step=5.0)
sideways_slope_threshold = input.float(title="Sideways Market Slope Threshold", defval=0.3, minval=0.1, step=0.1)
// Trend Catching Settings
enable_trend_catch = input.bool(title="Trend Catching System", defval=true)
trend_catch_adx_threshold = input.float(title="Trend Catch ADX Threshold", defval=30.0, minval=20.0, step=5.0)
trend_catch_di_diff = input.float(title="DI+ DI- Difference Threshold", defval=10.0, minval=5.0, step=2.5)
quick_entry_bars = input.int(title="Quick Entry Waiting Bars", defval=3, minval=1, maxval=10)
// ATR and Volatility Settings
atr_length = input.int(title="ATR Period", defval=14, minval=1)
atr_multiplier = input.float(title="ATR Multiplier", defval=2.0, minval=0.1, step=0.1)
// Ladder Trailing Stop Settings
ladder_step = input.float(title="Ladder Step Size (%)", defval=1.0, minval=0.1, step=0.1)
max_ladders = input.int(title="Maximum Ladder Count", defval=5, minval=2, maxval=10)
// Stop Loss and Take Profit Settings
use_stop_loss = input.bool(title="Use Stop Loss", defval=false)
use_take_profit = input.bool(title="Use Take Profit", defval=false)
use_trailing_stop = input.bool(title="Use Trailing Stop", defval=true)
sl_type = input.string(title="Stop Loss Type", defval="ATR", options=["ATR", "Percent", "Points"])
sl_atr_multiplier = input.float(title="SL ATR Multiplier", defval=2.0, minval=0.5, step=0.1)
sl_percent = input.float(title="SL Percent (%)", defval=2.0, minval=0.1, step=0.1)
sl_points = input.float(title="SL Points", defval=100, minval=1)
tp_type = input.string(title="Take Profit Type", defval="ATR", options=["ATR", "Percent", "Points", "Risk/Reward"])
tp_atr_multiplier = input.float(title="TP ATR Multiplier", defval=3.0, minval=0.5, step=0.1)
tp_percent = input.float(title="TP Percent (%)", defval=3.0, minval=0.1, step=0.1)
tp_points = input.float(title="TP Points", defval=150, minval=1)
tp_risk_reward = input.float(title="Risk/Reward Ratio", defval=2.0, minval=0.5, step=0.1)
// Horizontal Level Settings
horizontal_lookback = input.int(title="Horizontal Level Stabilization Period", defval=10, minval=3)
// ═══════════════════════════════════════════════════════════════════════════════
// INDICATORS AND CALCULATIONS
// ═══════════════════════════════════════════════════════════════════════════════
// ATR calculation
atr_value = ta.atr(atr_length)
// Moving Average calculation
ma_value = ma_type == "SMA" ? ta.sma(close, ma_length) : ma_type == "EMA" ? ta.ema(close, ma_length) : ma_type == "WMA" ? ta.wma(close, ma_length) : ta.ema(close, ma_length)
// ADX (Trend Strength) calculation - Manual calculation
tr = math.max(high - low, math.max(math.abs(high - close[1]), math.abs(low - close[1])))
plus_dm = high - high[1] > low[1] - low ? math.max(high - high[1], 0) : 0
minus_dm = low[1] - low > high - high[1] ? math.max(low[1] - low, 0) : 0
plus_di = 100 * ta.rma(plus_dm, adx_length) / ta.rma(tr, adx_length)
minus_di = 100 * ta.rma(minus_dm, adx_length) / ta.rma(tr, adx_length)
adx_value = 100 * ta.rma(math.abs(plus_di - minus_di) / (plus_di + minus_di), adx_length)
// MA slope calculation (for sideways market detection)
ma_slope = (ma_value - ma_value[5]) / ma_value[5] * 100
// Trend state detection
is_strong_trend = adx_value > trend_threshold
is_sideways_by_slope = math.abs(ma_slope) < sideways_slope_threshold
is_sideways = not is_strong_trend or is_sideways_by_slope
// Trend direction detection (DI+ vs DI-)
is_uptrend = plus_di > minus_di
is_downtrend = minus_di > plus_di
di_difference = math.abs(plus_di - minus_di)
// Strong trend momentum detection
strong_uptrend = adx_value > trend_catch_adx_threshold and plus_di > minus_di and di_difference > trend_catch_di_diff
strong_downtrend = adx_value > trend_catch_adx_threshold and minus_di > plus_di and di_difference > trend_catch_di_diff
// Position tracking system
var bool just_closed_long = false
var bool just_closed_short = false
var int bars_since_close = 0
// Position closure tracking - Fixed
if strategy.position_size == 0 and strategy.position_size[1] != 0
if strategy.position_size[1] > 0
just_closed_long := true
just_closed_short := false
else
just_closed_short := true
just_closed_long := false
bars_since_close := 0
else if strategy.position_size == 0
bars_since_close += 1
if bars_since_close > quick_entry_bars
just_closed_long := false
just_closed_short := false
else
just_closed_long := false
just_closed_short := false
bars_since_close := 0
// Ladder Average System
var float ladder_ma = na
var float horizontal_level = na
var int sideways_count = 0
// Trend-following ladder average
if is_strong_trend and not is_sideways_by_slope
// Normal MA tracking in strong trend
ladder_ma := ma_value
sideways_count := 0
else
// When trend weakens or in sideways market
sideways_count += 1
if sideways_count >= horizontal_lookback or na(horizontal_level)
horizontal_level := ma_value
ladder_ma := horizontal_level
// Market state
market_state = is_strong_trend and not is_sideways_by_slope ? "TREND" : "SIDEWAYS"
// Volatility measurement
volatility = atr_value / close * 100
// ═══════════════════════════════════════════════════════════════════════════════
// STOP LOSS AND TAKE PROFIT CALCULATIONS
// ═══════════════════════════════════════════════════════════════════════════════
// Stop Loss calculation function
calculate_stop_loss(entry_price_val, is_long) =>
sl_value = sl_type == "ATR" ? (is_long ? entry_price_val - (atr_value * sl_atr_multiplier) : entry_price_val + (atr_value * sl_atr_multiplier)) : sl_type == "Percent" ? (is_long ? entry_price_val * (1 - sl_percent / 100) : entry_price_val * (1 + sl_percent / 100)) : sl_type == "Points" ? (is_long ? entry_price_val - sl_points : entry_price_val + sl_points) : (is_long ? entry_price_val - (atr_value * sl_atr_multiplier) : entry_price_val + (atr_value * sl_atr_multiplier))
sl_adjusted = if is_sideways
is_long ? math.min(sl_value, ladder_ma - atr_value * 0.5) : math.max(sl_value, ladder_ma + atr_value * 0.5)
else
sl_value
sl_adjusted
// Take Profit calculation function
calculate_take_profit(entry_price_val, stop_loss_val, is_long) =>
tp_value = tp_type == "ATR" ? (is_long ? entry_price_val + (atr_value * tp_atr_multiplier) : entry_price_val - (atr_value * tp_atr_multiplier)) : tp_type == "Percent" ? (is_long ? entry_price_val * (1 + tp_percent / 100) : entry_price_val * (1 - tp_percent / 100)) : tp_type == "Points" ? (is_long ? entry_price_val + tp_points : entry_price_val - tp_points) : tp_type == "Risk/Reward" ? (is_long ? entry_price_val + (math.abs(entry_price_val - stop_loss_val) * tp_risk_reward) : entry_price_val - (math.abs(entry_price_val - stop_loss_val) * tp_risk_reward)) : (is_long ? entry_price_val + (atr_value * tp_atr_multiplier) : entry_price_val - (atr_value * tp_atr_multiplier))
tp_adjusted = if is_sideways
is_long ? math.max(tp_value, ladder_ma + atr_value * 1.5) : math.min(tp_value, ladder_ma - atr_value * 1.5)
else
tp_value
tp_adjusted
var float current_sl = na
var float current_tp = na
// ═══════════════════════════════════════════════════════════════════════════════
// ENTRY SIGNALS
// ═══════════════════════════════════════════════════════════════════════════════
// Normal entry conditions
normal_long = strategy.position_size == 0 and ((is_strong_trend and close > ladder_ma and close[1] <= ladder_ma[1]) or (is_sideways and close < ladder_ma and close > ladder_ma - atr_value))
normal_short = strategy.position_size == 0 and ((is_strong_trend and close < ladder_ma and close[1] >= ladder_ma[1]) or (is_sideways and close > ladder_ma and close < ladder_ma + atr_value))
// Trend catching entry conditions
trend_catch_long = enable_trend_catch and strategy.position_size == 0 and just_closed_short and bars_since_close <= quick_entry_bars and strong_uptrend and close > close[1] and close > ladder_ma
trend_catch_short = enable_trend_catch and strategy.position_size == 0 and just_closed_long and bars_since_close <= quick_entry_bars and strong_downtrend and close < close[1] and close < ladder_ma
// Strong momentum entry conditions (even if no position closed, but strong trend exists)
momentum_long = enable_trend_catch and strategy.position_size == 0 and strong_uptrend and close > ladder_ma and close > close[1] and close > open
momentum_short = enable_trend_catch and strategy.position_size == 0 and strong_downtrend and close < ladder_ma and close < close[1] and close < open
// Combined entry conditions
long_condition = normal_long or trend_catch_long or momentum_long
short_condition = normal_short or trend_catch_short or momentum_short
// Entry type determination
entry_type = if trend_catch_long or trend_catch_short
"TREND_CATCH"
else if momentum_long or momentum_short
"MOMENTUM"
else
market_state
// ═══════════════════════════════════════════════════════════════════════════════
// LADDER TRAILING STOP SYSTEM
// ═══════════════════════════════════════════════════════════════════════════════
var float[] ladder_levels = array.new<float>()
var float current_trailing_stop = na
var float entry_price = na
// Calculate ladder levels function
calculate_ladder_levels(entry_price_val, is_long) =>
ladder_array = array.new<float>()
base_level = ladder_ma
for i = 1 to max_ladders
level_value = if is_long
base_level + (atr_value * atr_multiplier * i * ladder_step / 100)
else
base_level - (atr_value * atr_multiplier * i * ladder_step / 100)
array.push(ladder_array, level_value)
ladder_array
// Trailing stop update function
update_trailing_stop(entry_price_val, current_price, is_long) =>
stop_level = if is_long
initial_stop = is_sideways ? ladder_ma - atr_value : entry_price_val - (atr_value * atr_multiplier)
new_stop = initial_stop
if array.size(ladder_levels) > 0
for i = 0 to array.size(ladder_levels) - 1
level_value = array.get(ladder_levels, i)
if current_price >= level_value
adjusted_stop = is_sideways ? ladder_ma : entry_price_val + (atr_value * atr_multiplier * (i + 1) * 0.3)
if adjusted_stop > new_stop
new_stop := adjusted_stop
new_stop
else
initial_stop = is_sideways ? ladder_ma + atr_value : entry_price_val + (atr_value * atr_multiplier)
new_stop = initial_stop
if array.size(ladder_levels) > 0
for i = 0 to array.size(ladder_levels) - 1
level_value = array.get(ladder_levels, i)
if current_price <= level_value
adjusted_stop = is_sideways ? ladder_ma : entry_price_val - (atr_value * atr_multiplier * (i + 1) * 0.3)
if adjusted_stop < new_stop
new_stop := adjusted_stop
new_stop
stop_level
// ═══════════════════════════════════════════════════════════════════════════════
// POSITION MANAGEMENT
// ═══════════════════════════════════════════════════════════════════════════════
// Long position entry
if long_condition
strategy.entry("Long", strategy.long, comment="Long: " + market_state)
entry_price := close
ladder_levels := calculate_ladder_levels(close, true)
// Stop Loss calculation (only if active)
if use_stop_loss
current_sl := calculate_stop_loss(close, true)
// Take Profit calculation (only if active)
if use_take_profit
temp_sl = use_stop_loss ? current_sl : close - (atr_value * sl_atr_multiplier)
current_tp := calculate_take_profit(close, temp_sl, true)
// Trailing stop initialization (only if active)
if use_trailing_stop
current_trailing_stop := is_sideways ? ladder_ma - atr_value : close - (atr_value * atr_multiplier)
// Short position entry
if short_condition
strategy.entry("Short", strategy.short, comment="Short: " + market_state)
entry_price := close
ladder_levels := calculate_ladder_levels(close, false)
// Stop Loss calculation (only if active)
if use_stop_loss
current_sl := calculate_stop_loss(close, false)
// Take Profit calculation (only if active)
if use_take_profit
temp_sl = use_stop_loss ? current_sl : close + (atr_value * sl_atr_multiplier)
current_tp := calculate_take_profit(close, temp_sl, false)
// Trailing stop initialization (only if active)
if use_trailing_stop
current_trailing_stop := is_sideways ? ladder_ma + atr_value : close + (atr_value * atr_multiplier)
// Position exit management
if strategy.position_size > 0 // Long position
// If using fixed SL/TP
if use_stop_loss and use_take_profit
strategy.exit("Long Exit", "Long", stop=current_sl, limit=current_tp, comment="SL/TP")
else if use_stop_loss and not use_take_profit
strategy.exit("Long Exit", "Long", stop=current_sl, comment="SL Only")
else if not use_stop_loss and use_take_profit
strategy.exit("Long Exit", "Long", limit=current_tp, comment="TP Only")
// If using trailing stop (optional)
if use_trailing_stop
current_trailing_stop := update_trailing_stop(entry_price, close, true)
if close <= current_trailing_stop
strategy.close("Long", comment="Trailing Stop")
if strategy.position_size < 0 // Short position
// If using fixed SL/TP
if use_stop_loss and use_take_profit
strategy.exit("Short Exit", "Short", stop=current_sl, limit=current_tp, comment="SL/TP")
else if use_stop_loss and not use_take_profit
strategy.exit("Short Exit", "Short", stop=current_sl, comment="SL Only")
else if not use_stop_loss and use_take_profit
strategy.exit("Short Exit", "Short", limit=current_tp, comment="TP Only")
// If using trailing stop (optional)
if use_trailing_stop
current_trailing_stop := update_trailing_stop(entry_price, close, false)
if close >= current_trailing_stop
strategy.close("Short", comment="Trailing Stop")
// ═══════════════════════════════════════════════════════════════════════════════
// VISUALIZATION
// ═══════════════════════════════════════════════════════════════════════════════
// Ladder Average plot
plot(ladder_ma, color=is_sideways ? color.orange : (ma_slope > 0 ? color.green : color.red), linewidth=3, title="Ladder Average")
// Horizontal level plot
plot(is_sideways ? horizontal_level : na, color=color.yellow, style=plot.style_circles, linewidth=2, title="Horizontal Level")
// ATR-based bands
upper_band = ladder_ma + atr_value
lower_band = ladder_ma - atr_value
plot(upper_band, color=color.new(color.blue, 70), title="Upper ATR Band")
plot(lower_band, color=color.new(color.blue, 70), title="Lower ATR Band")
// Stop Loss and Take Profit plots (only if active)
plot(strategy.position_size != 0 and use_stop_loss ? current_sl : na, color=color.red, style=plot.style_circles, linewidth=2, title="Stop Loss")
plot(strategy.position_size != 0 and use_take_profit ? current_tp : na, color=color.green, style=plot.style_circles, linewidth=2, title="Take Profit")
// Trailing stop plot (only if active)
plot(strategy.position_size > 0 and use_trailing_stop ? current_trailing_stop : na, color=color.orange, style=plot.style_stepline, linewidth=2, title="Long Trailing Stop")
plot(strategy.position_size < 0 and use_trailing_stop ? current_trailing_stop : na, color=color.orange, style=plot.style_stepline, linewidth=2, title="Short Trailing Stop")
// Market state background color
bgcolor(is_sideways ? color.new(color.yellow, 95) : (is_strong_trend ? color.new(color.green, 98) : color.new(color.gray, 98)), title="Market State")