首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >如何用 Python 回测抛物线 SAR 翻转策略?

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

原创
作者头像
用户7083614
发布于 2026-10-13 10:01:25
发布于 2026-10-13 10:01:25
260
举报

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

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

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

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

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

代码语言:python
复制
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 步:生成多空信号 → 次日开盘成交(避免未来函数)

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

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

代码语言:python
复制
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 步:输出绩效并与买入持有对比

代码语言:python
复制
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_max

SAR 加速因子步长/上限

0.02 / 0.20

FEE

单边手续费

0.0013

成交时点

避免未来函数

次日开盘

方向

A 股只做多头段

空仓代替做空

关键坑与注意事项

  • 避免未来函数:信号当日收盘出现,必须次日开盘成交;本文用 position.shift(1) 实现。
  • A 股不能裸做空:SAR 翻空时用「空仓」代替做空,别把做空收益算进去。
  • 手续费必扣:SAR 在震荡市翻转频繁,忽略费用会严重高估;单边 0.13% 仅为近似,按实际佣金调整。
  • 前复权价:未复权价在除权日让 SAR 与收益都失真。
  • 震荡市被反复打脸:SAR 是趋势工具,横盘时频繁翻转、交易成本高,可用 ADX 过滤无趋势阶段。
  • 单标的≠有效:应在多只、多区间验证,避免过拟合与幸存者偏差;本文为简化模型,未完全模拟 T+1、涨跌停无法成交等。

常见问题(FAQ)

Q:为什么不按当日收盘价成交?

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

Q:SAR 翻空为什么不做空?

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

Q:SAR 策略一定跑赢买入持有吗?

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

Q:需要付费吗?

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

小结

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

参考

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 为什么会有这个问题 / 传统做法的痛点
  • 分步骤解决(每步配可运行代码)
    • 第 1 步:取前复权日线并计算 SAR
    • 第 2 步:生成多空信号 → 次日开盘成交(避免未来函数)
    • 第 3 步:按次日开盘价 + 手续费计算净值
    • 第 4 步:输出绩效并与买入持有对比
  • 关键坑与注意事项
  • 常见问题(FAQ)
  • 小结
  • 参考
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档