工具篇(一)搞定了 AKShare 数据源,这一篇把整个量化研究闭环拉通:数据 → 入库 → 策略 → 回测 → 分析。选型上继续沿用之前提过的 vnpy——中国量化开源生态里最活跃的框架之一,覆盖了从行情接入、策略回测到实盘交易的整条链路。
策略上选一个经典的:海龟交易法(Turtle Trading)。1983 年理查德·丹尼斯在芝加哥拿这个策略训练了 13 个学员,四年赚了 1.75 亿美元,是趋势跟踪策略的鼻祖。规则简单到能写在一张 A4 纸上,但内含了系统化交易最重要的几个原则:信号明确、仓位管理、止损纪律。
内容顺序:装环境 → 建数据管道 → 写策略 → 用 Notebook 探索 → 发现一个反直觉的现象。全程实录,包括踩过的坑。
Table of contents
Open Table of contents
一、环境准备:合并到一个 venv
工具篇(一)里 AKShare 装在 ~/venvs/akshare。这一篇要用 vnpy,最合理的做法是两个包装在同一个 venv 里——因为核心用例就是”用 AKShare 拉数据 → 存进 vnpy 数据库 → 跑回测”,一个脚本里要同时 import 两边,必须共用环境。
分开 venv 的替代方案是”AKShare 导 CSV → vnpy 环境读 CSV → 入库”,多一次磁盘中转,没必要。
依赖冲突风险评估过:AKShare 和 vnpy 4.x 的核心依赖(pandas、numpy、requests)版本区间都比较宽松,pip 会自动选一个兼容版本,实际装起来不打架。vnpy 会额外拉 PySide6(Qt 图形库)和 ta-lib,跟 AKShare 没交集。
我选择把原来的 venv 目录改名为更中性的 ~/venvs/quant,反映它现在服务于整个量化工作流:
# venv 的 activate 脚本硬编码了绝对路径,直接 mv 会坏,用 pip freeze 重建
source ~/venvs/akshare/bin/activate
pip freeze > /tmp/pkgs.txt
deactivate
python3.13 -m venv ~/venvs/quant
source ~/venvs/quant/bin/activate
pip install -r /tmp/pkgs.txt
# 确认新环境没问题后再清理老的
rm -rf ~/venvs/akshare
顺手把 ~/.zshrc 里的别名也改掉:
sed -i '' 's|~/venvs/akshare/bin/activate|~/venvs/quant/bin/activate|g' ~/.zshrc
sed -i '' 's|alias akenv=|alias qenv=|g' ~/.zshrc
source ~/.zshrc
踩过的坑:venv 目录不能直接 mv 重命名,因为里面 bin/pip、bin/python3 这些脚本的 shebang(#!/Users/52coder/venvs/akshare/bin/python3)是硬编码的绝对路径。移动后 pip install 会立刻死。用 pip freeze 导出包列表、重建 venv、批量装回来是最干净的方案,30 秒搞定。
二、装 vnpy
vnpy 依赖 ta-lib 的 C 库,先用 Homebrew 装底层:
brew install ta-lib
vnpy 官方在 macOS 上不提供一键发行版(VeighNa Studio 只有 Windows),所以从源码装。clone 主仓库拿到 install_osx.sh:
cd ~/github
git clone https://github.com/vnpy/vnpy.git
cd vnpy
bash install_osx.sh
install_osx.sh 内部会升级 pip、装 numpy、装 ta-lib Python 绑定,最后 pip install . 装 vnpy 本体。默认从 vnpy 官方 PyPI 镜像源(pypi.vnpy.com)拉包,境外网络可能不通,可以换回官方源:
bash install_osx.sh python3 https://pypi.org/simple
vnpy 主仓库只是内核,具体的策略引擎、数据库适配器、交易接口都是独立子包,按需装:
pip install vnpy_ctastrategy vnpy_ctabacktester vnpy_sqlite
vnpy_ctastrategy:CTA 策略引擎(策略基类CtaTemplate在这里)vnpy_ctabacktester:CTA 回测引擎(BacktestingEngine在这里)vnpy_sqlite:SQLite 数据库适配器(vnpy 默认存储)
macOS 上跳过 CTP 接口:vnpy_ctp 是国内期货接口,新版 CTP 的 Mac API 改成了 framework 目录结构,PyPI 没预编译 wheel,需要本地 clone + 编译。研究 A 股 ETF 用不到 CTP,直接跳过省事。
验证装好:
python3 -c "
import akshare as ak
import vnpy
from vnpy_ctastrategy import CtaTemplate
from vnpy_ctastrategy.backtesting import BacktestingEngine
print(f'akshare {ak.__version__}')
print(f'vnpy {vnpy.__version__}')
print('backtesting engine imported OK')
"
输出:
akshare 1.18.64
vnpy 4.4.0
backtesting engine imported OK
三、目录结构约定
从这一篇开始,量化研究的代码放到一个固定目录,别再散在各处:
~/quant-research/
├── scripts/
│ ├── turtle_etf_strategy.py ← 策略类定义
│ └── fetch_and_backtest.py ← 命令行版本
├── notebooks/
│ └── turtle_backtest.ipynb ← 交互式实验
└── data/ ← 数据/结果导出
.py 和 .ipynb 分工不同,理解这一点很重要:
.py:定义可复用的策略类、工具函数。被 import 使用,很少直接单独跑。.ipynb:Jupyter Notebook,交互式实验环境。一格一格跑,看到中间结果、画图、调参。
策略代码写在 .py 里,Notebook 只做”实验主线”:import 策略、拉数据、跑回测、分析结果。这是量化研究行业里的通用模式,vnpy 官方的 examples/ 目录也是这样组织的。
四、海龟策略实现
先给策略骨架,参数含义放到后面详解。文件放到 ~/quant-research/scripts/turtle_etf_strategy.py:
"""海龟交易法 ETF 只做多版本"""
from vnpy_ctastrategy import (
CtaTemplate, StopOrder,
TickData, BarData, TradeData, OrderData,
BarGenerator, ArrayManager,
)
class TurtleEtfStrategy(CtaTemplate):
author = "52coder"
entry_window: int = 20 # 突破 20 日高点入场
exit_window: int = 10 # 跌破 10 日低点出场
atr_window: int = 20 # ATR 波动率计算周期
risk_per_trade: float = 0.02 # 每笔交易愿承担 2% 资金风险
fixed_capital: float = 100_000.0
entry_up: float = 0.0
exit_down: float = 0.0
atr_value: float = 0.0
long_stop: float = 0.0
parameters = ["entry_window", "exit_window", "atr_window",
"risk_per_trade", "fixed_capital"]
variables = ["entry_up", "exit_down", "atr_value", "long_stop"]
def on_init(self):
self.bg = BarGenerator(self.on_bar)
self.am = ArrayManager(size=max(self.entry_window, self.atr_window) + 10)
self.load_bar(self.entry_window + 20)
def on_start(self):
self.put_event()
def on_stop(self):
self.put_event()
def on_tick(self, tick):
self.bg.update_tick(tick)
def on_bar(self, bar):
self.cancel_all()
am = self.am
am.update_bar(bar)
if not am.inited:
return
# 唐奇安通道(不含当日)
self.entry_up = am.high_array[-self.entry_window:-1].max()
self.exit_down = am.low_array[-self.exit_window:-1].min()
self.atr_value = am.atr(self.atr_window)
if self.pos == 0:
# 空仓:突破入场
if bar.close_price > self.entry_up and self.atr_value > 0:
risk_cash = self.fixed_capital * self.risk_per_trade
unit_risk = 2 * self.atr_value
shares = int(risk_cash / unit_risk / 100) * 100
shares = max(shares, 100)
self.buy(bar.close_price, shares)
self.long_stop = bar.close_price - 2 * self.atr_value
elif self.pos > 0:
# 持仓:两个出场条件
if bar.close_price < self.exit_down or bar.close_price < self.long_stop:
self.sell(bar.close_price, abs(self.pos))
self.put_event()
def on_order(self, order): pass
def on_trade(self, trade): self.put_event()
def on_stop_order(self, stop_order): pass
对齐海龟策略的四个核心原则:
- 突破入场(20 日新高信号)
- 反向突破出场(10 日新低信号)
- ATR 波动率仓位管理(
shares = risk_cash / unit_risk) - 初始止损(
long_stop = 入场价 - 2 × ATR)
原版海龟同时做多做空,这里改成只做多——适配 A 股 ETF 现券账户规则。
五、五个参数的含义
entry_window = 20:入场信号窗口。策略每天看”过去 20 天的最高价”,当前收盘突破这条线就买入。这是海龟原版 System 1 的经典值。改小(比如 10)会更敏感、交易更频繁;改大(比如 55)只捕捉大级别趋势,交易极少。
exit_window = 10:出场信号窗口。持仓时每天看”过去 10 天的最低价”,跌破就卖出。出场窗口 < 入场窗口 是关键设计——“进得慢、退得快”,保住利润。
atr_window = 20:ATR 计算周期。ATR(Average True Range)是量化里最重要的波动率指标,衡量”最近 N 天平均每天波动多少”。用来两处:一处决定买多少、一处决定止损位。
risk_per_trade = 0.02:这是整个策略最关键的风控参数。规定”这笔交易如果亏,最多允许亏总资金的 2%“。交易界的经验值:1% 极保守、2% 稳健、5% 激进、10%+ 是赌博。
fixed_capital = 100_000.0:仓位计算的参考资金。策略每次买多少都基于这个数字算,故意不跟随账户浮盈浮亏变化——保持仓位稳定可预期,研究阶段这样更容易理解策略行为。
头寸计算的完整推演(假设 ETF 价格 4.00,ATR = 0.06):
risk_cash = 100_000 × 0.02 = 2000 元 ← 单笔允许亏损上限
unit_risk = 2 × 0.06 = 0.12 元/份 ← 每份 ETF 承担的风险
shares = 2000 / 0.12 / 100 × 100 = 1600 份
long_stop = 4.00 - 2 × 0.06 = 3.88 元 ← 初始止损位
如果 ETF 真的跌到 3.88 触发止损,亏损正好是 1600 × 0.12 = 192 元… 等等,为什么不是 2000?因为 int() 向下取整加上”100 份整数倍”约束,实际买的份数比理论值小,亏损也相应变小。这是保守设计——永远不会超过 2% 风险,只会更少。
六、数据 → 入库 → 回测:完整管道
策略写好后,用 AKShare 拉数据、写入 vnpy 数据库、跑回测。在 Notebook 里分 cell 组织:
Cell 1 — 路径配置(让 Notebook 找到 .py 文件)
import sys
from pathlib import Path
sys.path.insert(0, str(Path.home() / "quant-research" / "scripts"))
Cell 2 — 拉数据(用工具篇(一)里验证过的新浪源,境外可用)
import akshare as ak
import pandas as pd
from vnpy.trader.constant import Exchange, Interval
from vnpy.trader.database import get_database
from vnpy.trader.object import BarData
ETF_SYMBOL = "510300"
EXCHANGE = Exchange.SSE
df = ak.fund_etf_hist_sina(symbol=f"sh{ETF_SYMBOL}")
df["date"] = pd.to_datetime(df["date"])
df = df[(df["date"] >= "2020-01-01") & (df["date"] <= "2024-12-31")].reset_index(drop=True)
print(f"拉到 {len(df)} 根日线")
df.head()
输出 拉到 1212 根日线。注意新浪返回的字段里有 postVol、postAmt 两列全是 NaN——那是盘后成交量/成交额,A 股不支持盘后交易,这两列天然为空,接口里保留只是为了兼容有盘后交易的市场(美股 ETF 之类)。回测用不到,无视即可。
Cell 3 — 转 BarData 入库
bars = [
BarData(
gateway_name="AKSHARE_SINA",
symbol=ETF_SYMBOL, exchange=EXCHANGE,
datetime=row["date"].to_pydatetime(),
interval=Interval.DAILY,
volume=float(row["volume"]),
open_price=float(row["open"]),
high_price=float(row["high"]),
low_price=float(row["low"]),
close_price=float(row["close"]),
)
for _, row in df.iterrows()
]
get_database().save_bar_data(bars)
print(f"入库完成:{len(bars)} 根")
几个细节值得注意:
row["date"].to_pydatetime():pandas 的Timestamp转成 Python 原生的datetime,vnpy 的类型标注期望后者float(row["volume"]):把 numpy 类型显式转成 Python 原生 float,避免某些数据库序列化的边界问题save_bar_data是 UPSERT 语义(存在就更新、不存在就插入),主键是(symbol, exchange, datetime, interval)——所以这个脚本可以重复运行,不会产生重复数据
Cell 4 — 跑回测
from datetime import datetime
from vnpy_ctastrategy.backtesting import BacktestingEngine
from turtle_etf_strategy import TurtleEtfStrategy
engine = BacktestingEngine()
engine.set_parameters(
vt_symbol="510300.SSE",
interval=Interval.DAILY,
start=datetime(2021, 1, 1),
end=datetime(2024, 12, 31),
rate=2.5e-5, # ETF 手续费率
slippage=0.001, # 滑点
size=1, # ETF size=1
pricetick=0.001, # 最小报价单位
capital=100_000,
)
engine.add_strategy(TurtleEtfStrategy, {
"entry_window": 20, "exit_window": 10, "atr_window": 20,
"risk_per_trade": 0.02, "fixed_capital": 100_000.0,
})
engine.load_data()
engine.run_backtesting()
result_df = engine.calculate_result()
stats = engine.calculate_statistics()
回测引擎的调用是六步固定序列:new engine → set_parameters → add_strategy → load_data → run_backtesting → calculate_result/statistics。这个骨架在 Backtrader、Zipline、Backtesting.py 等其他框架里都一样,学一个就能触类旁通。
关键点:每次跑回测都要新建 engine,engine 内部有累积状态(成交记录、日报表、盈亏数据),复用会污染上一次的结果。这也是后面参数扫描时每次循环都要 engine = BacktestingEngine() 的原因。
跑完终端会打印统计指标:
起始资金: 100,000.00
结束资金: 106,891.35
总收益率: 6.89%
年化收益: 1.72%
最大回撤: -10,111
百分比最大回撤: -8.44%
总成交笔数: 14
Sharpe Ratio: 0.20
七、Jupyter 里的探索式研究
Cell 4 跑完只是起点,Notebook 真正的价值在于探索。
Cell 5 — 查看每日盈亏报表
result_df.tail(10)
这张表 vnpy 叫”逐日盯市盈亏报表”,是回测框架的核心输出。每一列对应一天的:
close_price/pre_close:当日/昨日收盘trades/trade_count:当日成交记录列表和笔数start_pos/end_pos:起止持仓turnover/commission/slippage:成交额、手续费、滑点trading_pnl:交易盈亏(成交价 vs 收盘价的差)holding_pnl:持仓盈亏(前日收盘 → 今日收盘的浮盈)total_pnl/net_pnl:当日总盈亏、扣成本后的净盈亏balance:累计账户余额(就是资金曲线的数据)highlevel/drawdown/ddpercent:历史最高、当前回撤金额、回撤百分比
Cell 6 — 画资金曲线
import matplotlib.pyplot as plt
# 关键:matplotlib 默认字体不含中文,会显示成方块
plt.rcParams["font.sans-serif"] = ["PingFang SC", "Heiti SC", "Arial Unicode MS"]
plt.rcParams["axes.unicode_minus"] = False
fig, ax = plt.subplots(figsize=(12, 5))
result_df["balance"].plot(ax=ax, color="steelblue", linewidth=1.5)
ax.set_title("海龟策略 - 沪深300ETF - 资金曲线")
ax.set_ylabel("账户余额 (元)")
ax.grid(alpha=0.3)
plt.tight_layout()
plt.savefig("balance_curve.png", dpi=150, bbox_inches="tight")
plt.show()
第一次画出的图上中文会全是方块,并伴随警告 Glyph xxx (\N{CJK UNIFIED IDEOGRAPH-xxxx}) missing——原因是 matplotlib 默认字体(DejaVu Sans)不含 CJK 字符。macOS 上加两行 rcParams 指定用系统自带的苹方(PingFang SC)就好。
八、一个反直觉的观察:资金曲线为什么长期平躺
画出来的资金曲线有一个很明显的特征:大部分时间是水平线,只在少数几个时段出现波动。特别是 2021-03 到 2022-06 这一整年多、2023-08 到 2024-03 跨年那半年多,balance 数值几乎完全不动。
刚开始容易怀疑”是不是回测有 bug”,但这是趋势跟踪策略的正常行为,不是 bug。
原因:海龟策略只在突破 20 日新高时买入、只在跌破 10 日新低时卖出。如果 ETF 一直在中间区域震荡(既不创新高也不创新低),策略啥也不干——没有交易 + 没有持仓 = 账户余额不变 = 资金曲线是水平线。
三种验证方法(由粗到细)
验证一:看交易日分布
trading_days = result_df[result_df["trade_count"] > 0]
print(f"总交易日:{len(result_df)}")
print(f"有交易的日子:{len(trading_days)} 天 ({len(trading_days)/len(result_df)*100:.1f}%)")
输出:
总交易日:969
有交易的日子:14 天 (1.4%)
14 笔交易分布在 969 个交易日里,98.6% 的时间在无所事事。这跟直觉相反——大部分新手看到这数字会想”策略是不是太懒了”,但这恰恰是趋势跟踪策略的核心哲学。
验证二:看持仓状态分布
in_position = result_df[result_df["end_pos"] != 0]
flat = result_df[result_df["end_pos"] == 0]
print(f"持仓状态:{len(in_position)} 天 ({len(in_position)/len(result_df)*100:.1f}%)")
print(f"空仓状态:{len(flat)} 天 ({len(flat)/len(result_df)*100:.1f}%)")
输出显示空仓状态大约占 95% 以上。海龟策略”少数时间在市场里、多数时间在观望”的特征就在这里量化出来。
验证三:可视化验证——把持仓期高亮到资金曲线上
fig, ax = plt.subplots(figsize=(14, 6))
# 资金曲线
ax.plot(result_df.index, result_df["balance"],
color="steelblue", linewidth=1.5, label="账户余额")
# 持仓时段涂成橙色背景
in_position = result_df["end_pos"] != 0
ax.fill_between(
result_df.index,
result_df["balance"].min() * 0.98,
result_df["balance"].max() * 1.02,
where=in_position, alpha=0.15, color="orange",
label="持仓期",
)
# 交易日标红点
trade_days = result_df[result_df["trade_count"] > 0]
ax.scatter(trade_days.index, trade_days["balance"],
color="red", s=30, zorder=5, label="交易日")
ax.set_title("海龟策略资金曲线(橙色=持仓期,红点=交易日)")
ax.set_ylabel("账户余额(元)")
ax.grid(alpha=0.3)
ax.legend(loc="upper left")
plt.tight_layout()
plt.savefig("position_highlight.png", dpi=150, bbox_inches="tight")
plt.show()

这张图一目了然:橙色(持仓期)恰好对应资金曲线有波动的部分,白色(空仓期)全部对应水平线。红点标出的每笔交易都在橙色区域的边界(买入是开始、卖出是结束)。
这个”不作为”其实是策略的核心价值
对比一下”活跃型”策略(比如高频/日内策略每天都在开平仓),趋势策略的交易日占比通常都不到 5%。这个**“不作为的绝大多数时间”,才是趋势策略能在大行情里赚到大钱的原因**——如果每天都在场内小打小闹,一次真趋势启动时可能已经在错误方向上被套住了。
用一句话概括趋势跟踪的哲学:
市场大多数时候在震荡,策略应该按兵不动等真趋势出现;一旦真趋势启动,就重仓吃到底。耐心 > 频繁。
理解了这个心智模型,就不会因为”策略最近没交易”而怀疑策略失效——空仓也是策略的重要组成部分,“不亏钱”本身就有价值。
九、参数扫描:找最合适的窗口组合
海龟经典参数是 20/10,但那是丹尼斯在 1980 年代美国期货市场验证的结果。A 股 ETF 是不是也是这套?扫一遍看看:
import pandas as pd
results = []
for entry_win in [10, 20, 55]:
for exit_win in [5, 10, 20]:
if exit_win >= entry_win:
continue
engine = BacktestingEngine()
engine.set_parameters(
vt_symbol="510300.SSE", interval=Interval.DAILY,
start=datetime(2021, 1, 1), end=datetime(2024, 12, 31),
rate=2.5e-5, slippage=0.001, size=1, pricetick=0.001,
capital=100_000,
)
engine.add_strategy(TurtleEtfStrategy, {
"entry_window": entry_win, "exit_window": exit_win,
"atr_window": 20, "risk_per_trade": 0.02,
"fixed_capital": 100_000.0,
})
engine.load_data()
engine.run_backtesting()
engine.calculate_result()
stats = engine.calculate_statistics(output=False)
results.append({
"entry": entry_win, "exit": exit_win,
"total_return": stats["total_return"],
"sharpe": stats["sharpe_ratio"],
"max_dd": stats["max_ddpercent"],
})
pd.DataFrame(results).sort_values("sharpe", ascending=False)
跑出来的结果:
| entry | exit | 总收益 (%) | Sharpe | 最大回撤 (%) |
|---|---|---|---|---|
| 20 | 10 | +6.89 | 0.20 | -10.11 |
| 20 | 5 | +4.97 | 0.16 | -9.70 |
| 10 | 5 | -0.07 | 0.00 | -11.54 |
| 55 | 5 | -7.82 | -0.54 | -8.59 |
| 55 | 10 | -8.60 | -0.59 | -9.36 |
| 55 | 20 | -11.45 | -0.73 | -12.21 |
结果解读
1. 短窗口效果显著好于长窗口
entry=20 的两组都盈利,entry=55 的三组全部亏损。原因是 A 股 ETF 趋势的持续性远不如美国期货市场,55 日突破意味着”要等更强的趋势才入场”,等到时趋势往往已经走了大半。
2. 唯一像样的组合正好是海龟经典参数 20/10
这也是研究里最有意思的观察之一:一个 1980 年代在美国期货市场设计的策略,40 年后在 A 股 ETF 上依然能找到对应的甜蜜点。趋势跟踪策略的结构性智慧跨越了市场和时代。
3. 即使最好的组合 Sharpe 也只有 0.2
这 4 年 A 股大部分时间在震荡(除了 2024 年 9 月那波),对趋势策略非常不友好。反复的假突破让策略一直在小亏,全靠 2024 年 9 月那一波抓到肉才勉强翻正。这不是策略的问题,是市场结构和策略适配度的问题。
参数扫描踩过的坑
扫完之后我想看看最佳组合具体每笔交易怎么走的:
trades = engine.get_all_trades()
for t in trades:
print(f"{t.datetime.date()} {t.direction.value} @{t.price:.3f} x{t.volume}")
结果打印出 14 笔交易全部亏损,跟前面 6.89% 收益完全对不上。愣了几秒才反应过来——engine 是循环里的变量,循环结束后指向最后一次迭代的实例,也就是表现最差的 entry=55, exit=20 那组。
想看最优组合的交易记录,得单独重新跑一次,不能从参数扫描的循环里拿。或者更工程化的做法是扫描时把 engine 存起来:
scan_results = {}
for entry_win in [10, 20, 55]:
for exit_win in [5, 10, 20]:
if exit_win >= entry_win:
continue
engine = BacktestingEngine()
# ... 参数设置 ...
engine.run_backtesting()
engine.calculate_result()
stats = engine.calculate_statistics(output=False)
scan_results[(entry_win, exit_win)] = {
"engine": engine, # 关键:保存 engine 实例
"stats": stats,
}
# 之后查任意组合的交易记录
best_engine = scan_results[(20, 10)]["engine"]
trades = best_engine.get_all_trades()
这个坑挺典型,量化研究里经常犯——循环里的变量作用域,跟你以为的可能不一样。
十、几个额外的排查经验
venv 隔离:~/venvs/xxx 目录之间的包互相看不见。不同 Notebook 用的 kernel 对应哪个 venv 要看清楚,选错的话会出现”明明装过却 ModuleNotFoundError”。Jupyter 左上角能看到当前 kernel 名,跟你想用的 venv 对上就行。
matplotlib 中文乱码:macOS 上加 plt.rcParams["font.sans-serif"] = ["PingFang SC"] 一行搞定。全局配置可以写在 ~/.matplotlib/matplotlibrc 里,一次配好所有 Notebook。
Notebook 里 import 找不到 .py 文件:在第一个 cell 里 sys.path.insert(0, str(...)) 把策略目录加进搜索路径。这是 Notebook 和脚本组织分离的标准做法。
engine 复用:绝对不要。每次跑回测都新建一个,前一次的成交记录、日报表都会污染下一次。
十一、下一步:从”跑通”到”跑好”
到这里量化研究的核心闭环彻底跑通了:AKShare 拉数据 → vnpy 数据库 → 策略 → 回测 → 参数扫描 → 结果分析。这个骨架能承载后续所有的策略研究。
接下来这个系列的方向:
- 策略层扩展:从最简单的双均线、海龟往上,叠加 MACD 背离、缠论笔段结构等信号做过滤
- 多标的组合:不只跑一个 ETF,同时跑几个不相关的标的(沪深300 + 中证500 + 创业板 + 港股)做资产配置
- 考虑真实约束:T+1、涨跌停、停牌等 A 股特有规则的建模
- 实盘信号发送:策略跑出信号后,用 vnpy 的 gateway 层对接实盘券商,或者简化成”每天生成交易信号推送到微信”
这一篇的两个附加素材:
- 用 Jupyter 做参数扫描的模式,能推广到任何回测框架
- “为什么资金曲线长期平躺”的可视化验证方法,可能是新手第一次接触趋势策略最需要建立的心智模型
工具篇(三)大概率会走多因子叠加这个方向。