回测脚本

Posted by Gregorius Blog on June 6, 2025

//@version=6 strategy(“Brooks_Final_V53_ThreeState_Logic”, overlay=true, pyramiding=2, initial_capital=100000, process_orders_on_close=true)

// — 1. 参数设置 — t0_enabled = input.bool(false, “开启T+0日内交易”) qty_per_hand = input.int(100, “每手股数”) risk_reward_ratio = input.float(1.0, “P1盈亏比”)

// — 2. 全局变量 — var float dynamic_res = 0.0 var float dynamic_sup = 0.0 var int range_test_count = 0 var int state = 1 // 1=区间, 2=上涨趋势, 3=下跌趋势 var int bull_count = 0 var float stop_level = 0.0 var float tp_p1 = 0.0 var int last_entry_date = 0

// — 3. 指标 — ema20 = ta.ema(close, 20) ema50 = ta.ema(close, 50) atr = ta.atr(14) h20 = ta.highest(high, 20) l20 = ta.lowest(low, 20)

// — 4. 动态边界 — // — 4.1. 动态初始化/定期重置 — if bar_index == 0 or bar_index % 50 == 0 dynamic_res := h20 dynamic_sup := l20

// — 4.2. 状态机判断 — float ma_20 = ta.sma(close, 20) float range_threshold = ta.atr(20) * 1.5 bool is_in_range = (high < (ma_20 + range_threshold)) and (low > (ma_20 - range_threshold))

// — 4.3. 支撑阻力更新逻辑 (纳入状态判断) — if is_in_range // 在区间内:我们要求更严格,只有明确打破区间边界才更新,减少噪音 if close > dynamic_res dynamic_res := close if close < dynamic_sup dynamic_sup := close else // 在趋势中:跟随趋势节奏,允许线位跟随移动 (类似于阶梯式止损) // 只有当价格突破时,才更新线位,保持滞后性,防止过早被踢出 if close > dynamic_res dynamic_res := close if close < dynamic_sup dynamic_sup := close

// — 4.4. 逻辑锁定优化 (防止线位在区间内漂移) — // 如果你觉得在区间内线位还是太乱,可以加入此锁定开关: var float locked_res = dynamic_res var float locked_sup = dynamic_sup

if is_in_range // 区间内只更新极值,保持平滑 dynamic_res := math.max(dynamic_res, high) dynamic_sup := math.min(dynamic_sup, low) else // 趋势中,动态调整线位 dynamic_res := close dynamic_sup := close

// 状态逻辑改进: // 1. 如果价格在区间阈值内,强制为状态 1(区间) // 2. 只有当价格突破这个阈值,且均线方向明确(slope)时,才切换为 2 或 3 bool is_trending_up = (close > (ma_20 + range_threshold)) and (ma_20 > ma_20[5]) bool is_trending_down = (close < (ma_20 - range_threshold)) and (ma_20 < ma_20[5])

if is_in_range state := 1 else if is_trending_up state := 2 else if is_trending_down state := 3

is_bull = close > open is_bear = close < open // 仅在上涨趋势下进行趋势回调计数 bull_count := (state == 2 and is_bear) ? bull_count + 1 : (is_bull ? 0 : bull_count)

// — 6. 进场决策 — // 1. 获取昨日收盘价 (确保在盘中可以正确获取日级别数据) float close_prev = request.security(syminfo.tickerid, “D”, close[1], barmerge.gaps_off, barmerge.lookahead_on) float daily_pct = (close - close_prev) / close_prev * 100 bool is_daily_runup_too_high = daily_pct > 7

// 2. K线强度与涨幅逻辑 float kline_change_pct = (close - open) / open * 100 // 实体占比 > 40% 且涨幅在 1% 到 7% 之间 bool is_strong_bar = (close > open) and (math.abs(close - open) / (math.max(high - low, 0.0001)) > 0.4) bool is_valid_growth = (kline_change_pct > 1.01 and kline_change_pct < 6.99)

bool is_breakout = (high > high[1])

// 3. 整合进场条件 // 共同限制:形态必须强、日内涨幅不能过高、且突破前高 bool common_entry_filters = is_strong_bar and is_valid_growth and not is_daily_runup_too_high and is_breakout

// 区间入场 // 关键改动:给区间入场增加一个不需要严格回调序列的“强力反转线”条件 bool is_ultra_strong_bar = (close > open) and (math.abs(close - open) / (math.max(high - low, 0.0001)) > 0.9) // 实体占比 > 90% // 要求该阳线不仅踩在支撑上,且具备决定性力度 bool climax_support_test = (is_ultra_strong_bar and common_entry_filters)

// 1. 计算上下影线长度 float lower_shadow = math.min(open, close) - low float upper_shadow = high - math.max(open, close) float body_size = math.abs(close - open)

// 2. 锤线/反转线判定逻辑 // A. 下影线至少是实体的 2 倍 // B. 下影线必须比上影线长 // C. 必须要刺破过支撑线 (low <= dynamic_sup) bool is_hammer_bar = is_bull and not is_daily_runup_too_high and (lower_shadow > body_size * 2) and (lower_shadow > upper_shadow) and (low <= dynamic_sup)

// 原有的区间入场(需严格回调序列) // 逻辑:只要当前K线收盘价低于前一根K线收盘价,就视为多头在“让步”或“试探”,此时计数增加 // 一旦出现阳线(is_bull)且该K线确认反转,计数器重置 // 定义支撑位临近度 (例如:在支撑位上方 1.5% 以内) float support_zone_limit = dynamic_sup * 1.015 bool is_near_support = (low <= support_zone_limit) range_test_count := (state == 1 and close < close[1]) ? range_test_count + 1 : (is_bull ? 0 : range_test_count) bool range_h1 = (state == 1 and range_test_count[1] == 1 and (climax_support_test or is_hammer_bar) and is_near_support) bool range_h2 = (state == 1 and range_test_count[1] >= 2 and (climax_support_test or is_hammer_bar) and is_near_support)

// 整合区间逻辑:满足其一即可 bool is_range_entry = (range_h1 or range_h2)

// 趋势入场 // 判定连续突破三次高点 bool is_continue_bull = is_bull and is_breakout and not is_daily_runup_too_high bool consecutive_bull = is_continue_bull and is_continue_bull[1] and is_continue_bull[2]

// 3. 趋势内入场逻辑整合: //支持“连续阳线”动能入场 bool momentum_signal = (state == 2 and consecutive_bull)

bool h1_signal = (state == 2 and is_bull and bull_count[1] == 1 and is_breakout and common_entry_filters) bool h2_signal = (state == 2 and is_bull and bull_count[1] >= 2 and is_breakout and common_entry_filters) bool h3_signal = (state == 2 and is_bull and bull_count[1] >= 3 and is_breakout and common_entry_filters) bool is_trend_entry = (h1_signal or h2_signal or h3_signal or momentum_signal or is_hammer_bar)

// 标记来源状态:在状态切换前记录当前 state var int prev_state = state if state != state[1] prev_state := state[1]

var bool is_in_cool_down = false var bool has_pulled_back = false // 新增一个标志,记录是否发生过回调

// 1. 监测冷却开启 if (prev_state == 3 and state == 2) is_in_cool_down := true

// 3. 解除冷却的真正条件:回调后且再次创新高 (即成功确认支撑) if is_in_cool_down and state != 2 is_in_cool_down := false

// 增加一个变量来追踪是否已经发生过初始突破 var bool has_initial_breakout = false

// 在每次状态发生变化(比如从区间变到趋势)时重置 if state != state[1] and state == 2 has_initial_breakout := false

// 关键改动:增加初始突破入场逻辑 bool initial_breakout_entry = (state == 2) and not has_initial_breakout and not is_in_cool_down and is_valid_growth and daily_pct < 9.95 and (close >= dynamic_res)

// 触发后锁定 if initial_breakout_entry has_initial_breakout := true

// 1. 获取日线 EMA(20) // 使用 request.security(“品种代码”, “D”, ta.ema(close, 10)) // 为了动态兼容,使用 syminfo.tickerid 获取当前品种 float daily_ema10 = request.security(syminfo.tickerid, “D”, ta.ema(close, 10))

// 2. 逻辑:日线趋势过滤 // 如果当前收盘价在日线 EMA 之下,说明大趋势在转弱或处于深度调整,禁止进场 bool is_daily_trend_bull = (close > daily_ema10)

var int trade_count_in_range = 0 if state != state[1] trade_count_in_range := 0 // 状态变了,计数器归零

// — 1. 计算实体涨幅 — // — 2. 计算过去 5 根 K 线的平均实体涨幅 — // ta.sma 是简单移动平均,这里用它来计算过去 5 根 K 线的平均实体 float avg_body_size_5 = ta.sma(body_size, 5)

// — 3. 定义入场过滤条件 — // 条件 A:必须是阳线 (close > open) // 条件 B:实体涨幅必须大于过去 5 根平均值的 1.5 倍(倍数可根据需求调整) bool is_strong_bull = (close > open) and (body_size > avg_body_size_5 * 1.5)

// — 强力突破判定 (Big Bar Takeover) — // 1. 计算实体是否足够大(吞没前 3 根 K 线) bool is_engulfing = (close - open) > (high[1] - low[1]) and (close - open) > (high[2] - low[2])

// 2. 计算回撤比例(最高到收盘的差值占波幅的比例) // 高位回撤 < 5% float high_to_close_pct = (high - close) / (high - low) * 100 bool is_strong_close = high_to_close_pct < 5.0

// 3. 合并成“大突破”逻辑 bool is_big_breakout = is_engulfing and is_strong_close

// 最终入场逻辑 go_long = (state != 3) and is_daily_trend_bull and ema20 > ema50 and close > ema20 and (initial_breakout_entry or (is_range_entry and trade_count_in_range <= 2 and is_strong_bull) or is_trend_entry)

// 在脚本开头定义全局变量 var string entry_type = “无”

// 判定逻辑(在你执行 strategy.entry 的地方) if go_long and strategy.position_size == 0 // 决定身份 if initial_breakout_entry entry_type := “突破” else if is_big_breakout entry_type := “超强” else if momentum_signal entry_type := “连阳” else entry_type := “回调” // 涵盖 H1/H2/H3

// 执行入场,同时把 entry_type 传给备注
strategy.entry("Long_P1", strategy.long, qty=qty_per_hand, comment=entry_type)

// — 加仓信号过滤器 — // — 逻辑:前三根至少两根小于当前根 — // 定义前三根成交量 float v1 = volume[1] float v2 = volume[2] float v3 = volume[3]

// 计数器:统计有多少根比当前成交量小 int count_lower = 0 if v1 < volume count_lower += 1 if v2 < volume count_lower += 1 if v3 < volume count_lower += 1

// 判定:如果计数 >= 2,说明当前成交量是这一轮的“放量”状态,且前三根有两根是“缩量”的 bool is_volume_confirm = count_lower >= 2

// 结合你的信号 bool add_trigger = (initial_breakout_entry or h2_signal) and is_volume_confirm

var float p3_entry_price = 0.0 // 2. 利用 ta.barssince 自动计数 // 只要 h2_trigger 满足,它就自动从 0 开始计数,不需要手动维护 int bars_since_p3 = ta.barssince(go_long and strategy.position_size >= 2 and add_trigger)

// — 7. 订单 — if go_long // 逻辑 A:完全没仓位,一次性建两笔仓 (P1 + P2) if strategy.position_size == 0 strategy.entry(“Long_P1”, strategy.long, qty=qty_per_hand, comment=”底仓_P1”) strategy.entry(“Long_P2”, strategy.long, qty=qty_per_hand, comment=”底仓_P2”) log.info(“【入场】初次入场:P1+P2 同时建立”)

// 逻辑 B:已有仓位,判断是否需要加第三笔 (P3)
// 只有在满足特定强势条件(例如:又是一个新高突破)时才进 P3
else if strategy.position_size >= 2 and add_trigger
    strategy.entry("Long_P3", strategy.long, qty=qty_per_hand, comment="加仓_P3")
    log.info("【入场】二次加仓:P3 已建立")
    p3_entry_price := close

stop_level := low - atr * 1
tp_p1 := close + (close - stop_level) * risk_reward_ratio
last_entry_date := year * 10000 + month * 100 + dayofmonth

// 1. 获取近10根 K 线的最高价 float highest_10 = ta.highest(high, 10)

// 2. 获取这 10 根之前的最高价(即判定基准) // 我们要看这 10 根是不是都低于“这 10 根之前的最高价” float highest_before_10 = ta.highest(high[10], 10)

// — 8. 离场 — if strategy.position_size > 0 stop_level := math.max(nz(stop_level, low - atr), low - atr) is_today = (year * 10000 + month * 100 + dayofmonth == last_entry_date) can_exit = t0_enabled or not is_today

// --- 动态离场监控 ---
// 1. 记录持仓期间的最高浮盈点 (High Water Mark)
var float highest_price_since_entry = 0.0
if strategy.position_size == 0
    highest_price_since_entry := 0.0
else
    highest_price_since_entry := math.max(high, nz(highest_price_since_entry, close))

// 计算当前指标
float avg_price = strategy.position_avg_price // 你的平均成本
float drawdown_from_peak = (highest_price_since_entry - close) / highest_price_since_entry * 100 // 从最高点回撤比例
float floating_profit_pct = (close - avg_price) / avg_price * 100 // 当前浮盈比例

// --- 3. 离场触发条件 ---
// A. 回撤太大(比如从高点回撤超过 3% 且已经有一定盈利,防止坐过山车)
// 优化:只有当“当前收盘价”与“历史最高收盘价”相比确实回撤很大时,才触发止盈
// 不再使用盘中最高价 (high),而是使用历史最高收盘价 (close)
var float highest_close_since_entry = 0.0
highest_close_since_entry := math.max(close, nz(highest_close_since_entry, close))

// 计算从最高收盘价的回撤
float drawdown_from_close_peak = (highest_close_since_entry - close) / highest_close_since_entry * 100

// 新增:今日收盘相对于昨收的跌幅
float pct_change_from_prev_close = (close[1] - close) / close[1] * 100

// 智能判定:
// 既要有盈利 (floating_profit_pct > 2.0)
// 又要满足:从最高点回撤超过 3% 且 今天收盘价大幅弱于昨天收盘 (比如今天跌幅超过 2%)
bool is_trailing_stop_hit = (floating_profit_pct > 2.0) and (drawdown_from_close_peak > 3.0) and (pct_change_from_prev_close > 2.0)

// 浮亏太大(比如入场后直接亏损 3%,上面定义的“纪律止损”)
bool is_loss_stop_hit = (floating_profit_pct < -3.0)

// --- 暴力离场逻辑 (Early Exit - 5% Drop) ---
// 1. 定义:必须是阴线 (close < open)
// 2. 定义:该 K 线自身跌幅 (高点到收盘) >= 5%
// 3. 定义:吞没前一根 K 线 (收盘价低于前一根阳线的开盘价)
float drop_pct = (high - close) / high * 100 // 当前 K 线高点到收盘的跌幅
bool is_bear_engulfing_severe = (close < open) and (drop_pct >= 5.0) and (close < low[1])
// 设置不同状态的止损阈值
// 突破入场(激进):容忍度较低,因为突破如果失效说明是假突破
float threshold = (entry_type == "突破" or entry_type == "超强") ? 2.5 : 4.0

// 计算当前浮亏
float current_drawdown = (avg_price - close) / avg_price * 100

// --- 加仓后的“失效监控” ---
// 如果持有 P3 已经超过 4 根 K 线,但价格始终没能创新高,说明突破失败
bool is_p3_stagnant = (bars_since_p3 >= 4) and (close < p3_entry_price)

// --- 阶梯止盈逻辑 ---
// 将计算结果强制转换为 int 类型
int last_trade_index = int(strategy.position_size - 1)

// 现在传给函数
float p3_avg_price = strategy.opentrades.entry_price(last_trade_index)

// 判定:如果相对于加仓价格,已经盈利了 5%
bool p3_take_profit_hit = (close - p3_avg_price) / p3_avg_price > 0.05

// 3. 触发判定:如果最近 10 根的最高点,没有超过过去 10 根的最高点
// 说明动能衰竭
bool is_no_new_high = (highest_10 <= highest_before_10)

if can_exit
    if close >= tp_p1
        strategy.close("Long_P1", "P1_TP")
    if close <= stop_level or close < dynamic_sup
        strategy.close_all("Exit_Stop")
    // 紧急离场
    else if is_bear_engulfing_severe
        strategy.close_all(comment="离场_5%暴跌吞没")
        log.info("【紧急离场】5%跌幅吞没触发 | 票: {0} | 跌幅: {1}%", syminfo.ticker, str.tostring(drop_pct))
    else if current_drawdown >= threshold
        strategy.close("Long_P1", comment="止损_" + str.tostring(threshold) + "%")
        log.info("【策略止损】根据入场状态执行 | 类型: {0} | 阈值: {1}%", entry_type, str.tostring(threshold))
    else if strategy.position_size  >= 3 and p3_take_profit_hit
        strategy.close("Long_P3", comment="P3_止盈") // 只平掉最后一笔加仓
        log.info("【部分止盈】P3 已出场")
    else if  is_trailing_stop_hit or is_loss_stop_hit
        string reason = is_trailing_stop_hit ? "移动止盈_回撤" : "亏损止损"
        strategy.close_all(comment=reason)
        log.info("【策略离场】原因: {0} | 现价: {1}", reason, str.tostring(close))
    else if strategy.position_size >= 2 and (is_p3_stagnant or close < p3_entry_price - atr * 1.5)
        strategy.close("Long_P3", comment="P3_盘整失效_止损")
        log.info("【加仓保护】P3 突破失效,及时止损")
    else if is_no_new_high
        strategy.close_all(comment="底仓_盘整超时_撤离")

// — 9. 画图 — // 绿色=上涨(2), 红色=下跌(3), 灰色=区间(1) color bg_color = state == 2 ? color.new(color.green, 90) : (state == 3 ? color.new(color.red, 90) : color.new(color.gray, 95)) bgcolor(bg_color)

plot(dynamic_res, color=color.blue, title=”Dynamic Resistance”, linewidth=2) plot(dynamic_sup, color=color.red, title=”Dynamic Support”, linewidth=2) plot(strategy.position_size > 0 ? stop_level : na, color=color.orange, linewidth=2, style=plot.style_linebr, title=”Trail Stop”) plot(strategy.position_size > 0 ? tp_p1 : na, color=color.green, linewidth=2, style=plot.style_linebr, title=”P1 TP”)

plotshape(go_long, style=shape.triangleup, location=location.belowbar, color=is_range_entry ? color.red : color.green, size=size.small, title=”Entry Marker”)

plot(stop_level, “Stop_Level_Debug”, display=display.data_window) plot(tp_p1, “TP_P1_Debug”, display=display.data_window)

// — 10. 最终版标签:全状态标注 — if go_long string label_text = “” color label_color = color.gray

if initial_breakout_entry
    label_text := "突破"
    label_color := color.teal // 亮青色,代表突破信号
else if momentum_signal
    label_text := "连阳"
    label_color := color.green
else if climax_support_test
    label_text := "强阳"
    label_color := color.blue
else if is_hammer_bar
    label_text := "倒垂"
    label_color := color.purple
else if h1_signal
    label_text := "H1"
    label_color := color.red
else if h2_signal
    label_text := "H2"
    label_color := color.orange
else if h3_signal
    label_text := "H3"
    label_color := color.yellow

// 绘制标签
label.new(bar_index, high, 
          text=label_text, 
          color=label_color, 
          textcolor=color.white, 
          style=label.style_label_down, 
          size=size.small)

/// — 10. 调试日志 (在 go_long 定义之后添加) — // — 核心诊断逻辑 — bool is_state_valid = (state != 3) bool is_daily_trend_ok = is_daily_trend_bull bool is_ema_ok = (ema20 > ema50 and close > ema20)

// 决定性入场路径 bool can_initial = (initial_breakout_entry) bool can_range = (is_range_entry and trade_count_in_range <= 2 and is_strong_bull) bool can_trend = (is_trend_entry)

// 详细诊断日志 if ta.change(go_long) and go_long log.info(“【入场诊断】票名: {0} | 状态: {1} | EMA多头: {2} | 日线趋势: {3} | 路径: {4}”, syminfo.ticker, state, is_ema_ok, is_daily_trend_ok, can_initial ? “Initial” : (can_range ? “Range” : “Trend”))

// 如果 go_long 没触发,但你觉得应该触发,查这里: if not go_long and (state == 2 or state == 1) and barstate.isconfirmed log.info(“【入场拒绝】票名: {0} | 原因: EMA多头={1}, 日线={2}, 初始={3}, 区间={4}, 趋势={5}”, syminfo.ticker, is_ema_ok, is_daily_trend_ok, can_initial, can_range, can_trend)