加了止损止盈,回测结果会变好吗?用 Python 在同一策略上做 A/B 对比
加了止损止盈,回测结果会变好吗?
一句话回答: 止损止盈不是「万能保险」——它改变的是收益分布的形状(砍掉极端大亏、也砍掉极端大赚),不保证提高总收益。正确的验证方法是在同一入场规则上做 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 | 单边手续费 | 买卖各扣一次 |
关键坑与注意事项
- A/B 必须单一变量:两组只差「有没有止损止盈」,否则对比无意义。
- 未来函数:触发判断用已收盘价、成交用次日开盘。
- 止损参数敏感:8%/15% 只是示例,不同比例结论可能反转;应做参数敏感性分析。
- 结果不可外推:单标的单区间的「少亏」不代表止损普遍有效,需多标的、多区间检验。
- 日内跳空:本文按次日开盘成交,极端跳空会使实际止损偏离设定值(滑点)。
常见问题(FAQ)
Q:为什么加了止损胜率没变? A:止损止盈改变的是「每笔赚多少/亏多少」,不改变「入场方向对不对」。胜率由入场规则决定,所以这里几乎不变。
Q:止损应该用固定百分比还是 ATR?
A:固定百分比简单直观;ATR 跟随波动率自适应(见仓库 atr-trailing-stop-backtest)。本文聚焦固定百分比的 A/B 方法论。
Q:需要付费吗?
A:单只股票日线在免费额度内即可;若要对全市场批量做止损止盈参数网格搜索,需要 klines.batch + universes(Starter 及以上),详见官网定价页。
小结
止损止盈不是保险,而是对收益分布的「两端裁剪」。用同一入场规则做单变量 A/B 回测,才能看清它在你的策略上是「少亏」还是「少赚」。有了 AlphaFeed 的前复权数据 + 次日开盘成交 + 手续费,几十行就能跑出诚实的对比——切忌把单次结果外推成普适结论。
相关链接:
- AlphaFeed 官网:https://alphafeed.org/
- Python SDK 快速开始:https://docs.alphafeed.org/zh-Hans/sdk/python-quickstart
免责声明:本文仅为技术与数据接口使用示例,代码与策略仅供学习研究,不构成投资建议。市场有风险,历史数据不代表未来表现。