AlphaFeed Blog

如何用 Python 回测抛物线 SAR 翻转策略?(多空翻转 + 移动止损,含手续费)

策略与回测 · 代码验证于 2026-09-25

如何用 Python 回测抛物线 SAR 翻转策略?

一句话回答: SAR(Stop And Reverse)天生是「翻转」信号——价格升破 SAR 做多、跌破 SAR 平仓(A 股无法裸做空,只做多头段)。用 AlphaFeed 取回前复权日线算 SAR,用「当日出信号、次日开盘成交」避免未来函数,扣手续费,几十行就能回测出收益、最大回撤与交易次数,并和买入持有对比检验它值不值。

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

SAR 常被当「移动止损 + 趋势跟踪」用,但「按 SAR 翻转做多做空到底赚不赚钱」要回测验证。两个常见错误:一是未来函数(当日出信号用当日收盘价成交,实际做不到);二是忽略 A 股限制与手续费(不能裸卖空、换手有成本)。正确做法:前复权价算 SAR、次日开盘成交、只做多头段、扣费、对比基准。

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

第 1 步:取前复权日线并计算 SAR

from alphafeed import AlphaFeed
import pandas as pd

af = AlphaFeed()  # 读取 ALPHAFEED_API_KEY

df = af.klines.get("600519.SH", period="1d", count=500,
                   adjust="forward", to_dataframe=True)
df = df.sort_values("trade_date").reset_index(drop=True)

def parabolic_sar(high, low, af_step=0.02, af_max=0.20):
    high = high.to_numpy(); low = low.to_numpy(); n = len(high)
    sar = [0.0] * n; trend_up = True; af = af_step; ep = high[0]; sar[0] = low[0]
    for i in range(1, n):
        prev = sar[i - 1]; sar[i] = prev + af * (ep - prev)
        if trend_up:
            sar[i] = min(sar[i], low[i - 1], low[max(i - 2, 0)])
            if high[i] > ep: ep = high[i]; af = min(af + af_step, af_max)
            if low[i] < sar[i]: trend_up = False; sar[i] = ep; ep = low[i]; af = af_step
        else:
            sar[i] = max(sar[i], high[i - 1], high[max(i - 2, 0)])
            if low[i] < ep: ep = low[i]; af = min(af + af_step, af_max)
            if high[i] > sar[i]: trend_up = True; sar[i] = ep; ep = high[i]; af = af_step
    return pd.Series(sar, index=range(n))

df["SAR"] = parabolic_sar(df["high"], df["low"])

第 2 步:生成多空信号 → 次日开盘成交(避免未来函数)

# 收盘价在 SAR 上方=多头持有,下方=空仓(A 股不裸做空)
df["long"] = (df["close"] > df["SAR"]).astype(int)
df["position"] = df["long"].shift(1).fillna(0)   # 当日信号,次日才持仓

第 3 步:按次日开盘价 + 手续费计算净值

FEE = 0.0013  # 单边约 0.13%(佣金+印花税近似)
df["open_ret"] = df["open"].pct_change().shift(-1)      # 持仓吃 t→t+1 开盘间收益
df["trade"] = df["position"].diff().abs().fillna(0)     # 换手(建仓/平仓)
df["strat_ret"] = df["position"] * df["open_ret"].fillna(0) - df["trade"] * FEE
df["equity"] = (1 + df["strat_ret"]).cumprod()
df["bench"] = (1 + df["close"].pct_change().fillna(0)).cumprod()  # 买入持有基准

第 4 步:输出绩效并与买入持有对比

def stats(equity, rets):
    total = equity.iloc[-1] - 1
    cagr = equity.iloc[-1] ** (252 / len(equity)) - 1
    dd = (equity / equity.cummax() - 1).min()
    return total, cagr, dd

t_s, c_s, d_s = stats(df["equity"], df["strat_ret"])
t_b, c_b, d_b = stats(df["bench"], df["close"].pct_change())
trades = int(df["trade"].sum() // 2)
print(f"SAR策略  收益{t_s:.2%} 年化{c_s:.2%} 回撤{d_s:.2%} 交易{trades}次")
print(f"买入持有 收益{t_b:.2%} 年化{c_b:.2%} 回撤{d_b:.2%}")

关键参数:

参数含义本文取值
af_step / af_maxSAR 加速因子步长/上限0.02 / 0.20
FEE单边手续费0.0013
成交时点避免未来函数次日开盘
方向A 股只做多头段空仓代替做空

关键坑与注意事项

常见问题(FAQ)

Q:为什么不按当日收盘价成交? A:当日收盘才算出信号,用当日收盘价成交是未来函数。用次日开盘价才真实可执行。

Q:SAR 翻空为什么不做空? A:A 股个股无法裸做空(融券成本高、标的有限),所以翻空时用空仓代替,只赚多头段。

Q:SAR 策略一定跑赢买入持有吗? A:不一定。趋势强的品种/区间 SAR 能控回撤,震荡市则因频繁交易而跑输。回测对比才有结论。

Q:需要付费吗? A:单只历史日线有免费额度;多标的批量回测(klines.batch + universes)需 Starter 及以上。详见定价页。

小结

SAR 翻转策略回测的关键在严谨性:前复权价算 SAR、次日开盘成交避免未来函数、A 股只做多头段、扣手续费、和买入持有对比。用 AlphaFeed 取回前复权日线,几十行 Python 就能客观检验这套经典趋势跟踪信号在具体品种上到底值不值。

相关链接:


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