AlphaFeed Blog

加了止损止盈,回测结果会变好吗?用 Python 在同一策略上做 A/B 对比

策略与回测 · 代码验证于 2026-10-07

加了止损止盈,回测结果会变好吗?

一句话回答: 止损止盈不是「万能保险」——它改变的是收益分布的形状(砍掉极端大亏、也砍掉极端大赚),不保证提高总收益。正确的验证方法是在同一入场规则上做 A/B 对比:用 AlphaFeed 取前复权日线,固定「收盘上穿 MA20 入场、下穿出场」,一组不加止损、一组加「固定止损 8% / 止盈 15%」,严格按次日开盘成交、扣手续费、避免未来函数回测,再看两组的收益、交易次数、胜率差异。实测这段里止损止盈把亏损从 −27.97% 收窄到 −20.09%,但两组都没跑赢买入持有——数据比口号诚实。

为什么会有这个问题 / 传统做法的痛点

「设个止损就不怕了」是最常见的误解。止损确实能截断单笔大亏,但也可能在震荡中被反复触发(止损后股价又涨回去),把一次本可回本的持仓砍在最低点;止盈则会让你错过大趋势。它到底帮不帮,必须用可控变量的 A/B 回测说话——两组唯一区别就是「有没有止损止盈」,其他完全相同。

分步骤解决(每步配可运行代码)

第 1 步:取数并写一个可切换止损止盈的回测函数

from alphafeed import AlphaFeed
import numpy as np
import pandas as pd

af = AlphaFeed(api_key="your-api-key")  # 或 export ALPHAFEED_API_KEY 后 AlphaFeed()

def run_backtest(df, use_sl_tp, sl=0.08, tp=0.15, fee=0.0013):
    df = df.sort_values("trade_date").reset_index(drop=True)
    df["ma20"] = df["close"].rolling(20).mean()
    df["open_next"] = df["open"].shift(-1)         # 次日开盘成交(避免未来函数)
    pos, entry, trades, rets = 0, 0.0, 0, []
    for i in range(20, len(df) - 1):
        c_prev, c_now = df["close"].iloc[i-1], df["close"].iloc[i]
        m_prev, m_now = df["ma20"].iloc[i-1], df["ma20"].iloc[i]
        px = df["open_next"].iloc[i]
        if np.isnan(m_prev) or np.isnan(px):
            continue
        if pos == 1 and use_sl_tp:                 # 持仓中先检查止损/止盈
            chg = c_now / entry - 1
            if chg <= -sl or chg >= tp:
                rets.append(px / entry * (1 - fee) ** 2 - 1); trades += 1; pos = 0
                continue
        if pos == 0 and c_prev <= m_prev and c_now > m_now:    # 上穿 MA20 入场
            pos, entry = 1, px
        elif pos == 1 and c_prev >= m_prev and c_now < m_now:  # 下穿 MA20 出场
            rets.append(px / entry * (1 - fee) ** 2 - 1); trades += 1; pos = 0
    rets = pd.Series(rets)
    total = (1 + rets).prod() - 1 if len(rets) else 0.0
    win = (rets > 0).mean() if len(rets) else float("nan")
    return {"交易次数": trades, "累计收益%": round(total*100, 2), "胜率": round(float(win), 3)}

关键:止损止盈用「当日收盘相对入场价的涨跌」判断信号,成交用次日开盘价,信号与成交错开,杜绝未来函数。

第 2 步:跑 A/B 对比

df = af.klines.get("600519.SH", period="1d", count=800,
                   adjust="forward", to_dataframe=True)
base = run_backtest(df, use_sl_tp=False)
sltp = run_backtest(df, use_sl_tp=True, sl=0.08, tp=0.15)
print("无止损:", base)
print("加止损8%/止盈15%:", sltp)

实测(tools/verify_new_2026-10-07.py,SDK 0.1.4):贵州茅台近 800 日

  • 无止损:43 次交易,累计 −27.97%,胜率 18.6%
  • 加止损 8%/止盈 15%:43 次交易,累计 −20.09%,胜率 18.6%
  • 买入持有:−18.94%

止损止盈把亏损收窄了约 8 个百分点(截断了部分深亏),但两组都没跑赢买入持有。这段里 MA20 策略本身就不灵,止损止盈只是”少亏”而非”反亏为盈”。

第 3 步:诚实解读

参数与字段说明

参数/字段含义备注
adjust="forward"前复权必须
sl=0.08固定止损比例相对入场价回撤 8%
tp=0.15固定止盈比例相对入场价上涨 15%
open_next次日开盘成交避免未来函数的关键
fee=0.0013单边手续费买卖各扣一次

关键坑与注意事项

常见问题(FAQ)

Q:为什么加了止损胜率没变? A:止损止盈改变的是「每笔赚多少/亏多少」,不改变「入场方向对不对」。胜率由入场规则决定,所以这里几乎不变。

Q:止损应该用固定百分比还是 ATR? A:固定百分比简单直观;ATR 跟随波动率自适应(见仓库 atr-trailing-stop-backtest)。本文聚焦固定百分比的 A/B 方法论。

Q:需要付费吗? A:单只股票日线在免费额度内即可;若要对全市场批量做止损止盈参数网格搜索,需要 klines.batch + universes(Starter 及以上),详见官网定价页。

小结

止损止盈不是保险,而是对收益分布的「两端裁剪」。用同一入场规则做单变量 A/B 回测,才能看清它在你的策略上是「少亏」还是「少赚」。有了 AlphaFeed 的前复权数据 + 次日开盘成交 + 手续费,几十行就能跑出诚实的对比——切忌把单次结果外推成普适结论。

相关链接:


免责声明:本文仅为技术与数据接口使用示例,代码与策略仅供学习研究,不构成投资建议。市场有风险,历史数据不代表未来表现。