Python 量化金融市场基础

代码之外,量化交易者必须理解市场本身的规则:有什么资产可以交易、什么时候能交易、价格为什么会跳空、每笔交易要付出多少成本。

这些规则直接决定策略的写法——不考虑 T+1 和涨跌停的回测,结论是不可信的。


金融资产大类

量化交易的对象是各种金融资产,常见的有以下几类:

资产 是什么 风险水平
股票 公司所有权凭证,收益来自价差与分红 中高
债券 债务凭证,到期还本付息,价格与利率反向 低
公募基金 / ETF 一篮子资产的集合投资,ETF 可像股票一样场内交易 视底层资产而定
期货 约定未来某时点以某价格买卖的标准化合约,自带杠杆、可双向交易 高
期权 买入"在未来以约定价格买卖的权利",买方最大损失为权利金 高(卖方风险极大)
外汇 不同国家货币之间的兑换交易,全球最大交易市场 中高
加密货币 7×24 小时交易、无涨跌停限制、波动极大 极高

本教程的实例以股票为主,这也是个人量化入门最合适的品种。


A 股交易规则

写策略前先记住 A 股的几条硬规则:

规则 内容 对策略的影响
交易时段 工作日 9:30-11:30、13:00-15:00 日线策略以"交易日"为单位
T+1 当天买入的股票次日才能卖出 无法当天买当天卖,日内回转受限
涨跌停 主板 ±10%,创业板/科创板 ±20%,ST 股 ±5% 涨停买不进、跌停卖不出,回测要模拟
最小单位 买入须为 100 股(1 手)整数倍,卖出可不足 100 股 仓位计算要按手取整
价格最小变动 0.01 元 限价单价格只能落在 0.01 的网格上

其中涨跌停对回测影响最大:跌停日股票"卖不出去",策略的实际执行可能与信号不一致,严谨的回测框架会专门处理这一点。

全球主要市场规则对比

规则 A 股 港股 美股 加密货币
结算制度 T+1 T+2 交易,可日内回转 T+1 结算,可日内回转 实时
涨跌幅限制 ±10% / ±20% 无(有市调机制) 无(有熔断机制) 无
最小单位 100 股 1 手(各股不同) 1 股(支持碎股) 任意数量
做空 融券(受限) 可卖空 可卖空 合约可做空
交易时间 4.5 小时/日 5.5 小时/日 6.5 小时/日 7×24

提示:市场差异意味着策略不能直接照搬。美股上验证的日内策略拿到 A 股会受 T+1 限制;A 股的涨跌停规则在加密货币市场则完全不存在。跨市场移植策略时,先核对规则表。


复权:回测前的必修课

第 6 篇介绍数据获取时提过复权,这里把它彻底讲清楚。

上市公司分红、送股后,股价会在除权除息日出现人为的"跳空下跌":比如每股分红 25 元,除权日开盘价会直接比前一日收盘价低 25 元左右。

这个跳空不是市场下跌,如果直接用未复权价格计算收益率,每次分红都会被误判成一次暴跌。

复权就是消除这个人为跳空的处理方式,常用两种口径:

口径 做法 用途
前复权 以当前价格为基准,向前调整历史价格 看图、计算收益率,最常用
后复权 以上市首日为基准,向后调整 计算长期真实总回报
不复权 原始价格 查看历史真实成交价

动手验证:茅台的分红跳空

贵州茅台在 2023 年 6 月 30 日除权除息,每股派现约 25.9 元。对比除权日前后的未复权价与复权价:

实例

import yfinance as yf

# auto_adjust=False 同时返回未复权 Close 与复权 Adj Close
df = yf.download("600519.SS", start="2023-06-01", end="2023-07-15",
                 auto_adjust=False, progress=False, multi_level_index=False)

print(df[["Close", "Adj Close"]].round(2).to_string())

# 查看分红记录
print(yf.Ticker("600519.SS").dividends.loc["2023"].to_string())

输出结果如下所示(节选除权日前后):

             Close  Adj Close
Date
2023-06-27  1711.05    1513.83
2023-06-28  1728.38    1529.16
2023-06-29  1713.71    1516.19
2023-06-30  1691.00    1519.06
2023-07-03  1724.10    1548.80
2023-07-04  1730.60    1554.63

2023-06-30    25.911

未复权 Close 在 6 月 30 日从 1713.71 跌到 1691.00,看似下跌 1.3%;而复权价 Adj Close 实际从 1516.19 涨到 1519.06——除权日持有者真实是赚钱的,分红已经落袋。

把 2019 至 2024 年的两条价格曲线画在一起,除权日的大量"假下跌"清晰可见:

实例

import yfinance as yf
import matplotlib.pyplot as plt

df = yf.download("600519.SS", start="2019-01-01", end="2024-12-31",
                 auto_adjust=False, progress=False, multi_level_index=False)

plt.figure(figsize=(12, 6))
plt.plot(df.index, df["Close"], label="Close (Unadjusted)",
         color="#1f2933", linewidth=1.2)
plt.plot(df.index, df["Adj Close"], label="Adj Close (Adjusted)",
         color="#14805e", linewidth=1.2)
plt.title("Maotai (600519.SS): Close vs Adj Close 2019-2024")
plt.xlabel("Date")
plt.ylabel("Price (CNY)")
plt.legend()
plt.grid(alpha=0.25)
plt.show()

执行以上代码,输出的图表如下:

茅台未复权与复权价格对比

两条曲线的裂口都出现在每年 6 月前后的分红除权日。未复权曲线越往后"跌得越多",纯属分红累积的假象。

结论:计算收益率、均线、回测一律使用复权价格;只有查看"历史上真实成交了多少块钱"时才看未复权价。yfinance 用 auto_adjust 参数控制,AKShare 用 adjust="qfq"(前复权)/"hfq"(后复权)控制,详见获取金融数据。


交易成本:策略的隐形对手

回测里每一笔交易都不是免费的,A 股交易的主要成本如下:

费用 费率 收取方向
佣金 常见万 2.5 至万 3,多数券商有 5 元起收门槛 买卖双边
印花税 0.05% 仅卖出单边
过户费 0.001% 买卖双边
滑点 无固定值,取决于流动性 隐性成本

双边合计约 0.06% 至 0.1%,单边卖出再加 0.05% 印花税。

换个角度看:一次完整的买卖循环大约损耗 0.13% 到 0.16%。年换手 100 次的策略,光成本就要吃掉 13% 到 16% 的收益。

注意:以上费率以券商与交易所最新公告为准。滑点指实际成交价与信号价格的偏差,流动性差的股票或快速波动的行情中滑点可能远超手续费。回测时务必设置与实际一致的费率。