凌晨 2 点,你刚把策略回测跑完。收益率 47%,夏普比率 3.2。准备睡觉前看了一眼资金曲线——漂亮。
第二天早上醒来:收益率 12%,夏普 1.4。
你以为 BUG 在代码里。实际 BUG 在你的金融知识里。
这是程序员转量化最常踩的坑:代码写得对,数据理解错了。 前复权?后复权?不复权?为什么回测里"精准"买入的位置,实盘根本成交不了?为什么"分红送股"会让历史价格突然打折?
金融概念不需要你读完一个金融学位。但有 5 个概念,如果你跳过它们,回测系统从第一天起就在骗你。
程序员转量化的金融知识速成指南:你只需要懂这 5 个概念
一、先说清楚:为什么程序员最容易在这里翻车
程序员写代码,习惯"非黑即白"。一个函数要么返回 200,要么返回 404。
金融数据不讲这个道理。
同一只股票,历史价格可能同时有三个数字:前复权价、后复权价、不复权价。三个都是对的,但三个适合的场景完全不同。你选错了,回测结果就是空中楼阁。
这不是智商问题,是认知框架的错位。程序员不熟悉金融数据的"测量误差",就像金融人不理解为什么代码要写单元测试。
下面这 5 个概念,每个都对应一个程序员式的"理解为什么":
二、概念一:复权——历史价格的"汇率换算"
2.1 问题是怎么来的
假设苹果公司(AAPL)五年前股价是 $50,今天是 $220。如果你直接画一张 K 线图,会看到一条陡峭的上涨曲线——好像买了就赚。
但这个图漏了一个关键信息:AAPL 在这五年里进行了 2 次拆股(1 拆 4),还分了 12 次股息。
不处理这些事件,直接用"原始价格"画图,就相当于把人民币和美元混在一起画折线图——数字上去了,但实际上没有可比性。
复权,就是给历史价格做一个"汇率换算",让不同时间点的价格可以放在同一个坐标系里比较。
2.2 前复权 vs 后复权:两种换算方向
| 类型 | 换算方向 | 视觉表现 | 适用场景 |
|---|---|---|---|
| 前复权 | 把历史价格往"今天"靠 | 历史价格变小 | 最常用,技术分析、策略回测 |
| 后复权 | 把今天价格往"历史"靠 | 今天价格变大 | 长期价值投资、股息再投入分析 |
前复权的逻辑:以现在的价格为准,向下调整历史价格。 就像把所有年份的 GDP 都换算成今天美元计价,让你一眼看出增长曲线。
# 模拟复权的原理(简化演示,不含完整除权因子计算)
def forward_adjust_price(raw_prices, split_ratio_history):
"""
前复权:将历史价格向下调整
原理:除权因子 = 拆分后股价 / 拆分前股价
前复权价 = 原始价 × 除权因子累积值
"""
adjusted = []
cumulative_factor = 1.0
# 逆序处理,从最近到最远
for price, split_ratio in zip(reversed(raw_prices), reversed(split_ratio_history)):
# 每遇到一次拆股,累积因子乘以拆股比例
cumulative_factor *= split_ratio
# 前复权价 = 当前价 / 当时累积因子(简化)
adjusted.append(price * cumulative_factor)
return list(reversed(adjusted))
# 真实场景中,TickDB 的 kline 接口直接返回已复权数据
import os
import requests
# TickDB 获取前复权 K 线数据
# interval: 1d=日线, 1h=小时线; adjust=forward 前复权, backward 后复权
response = requests.get(
"https://api.tickdb.ai/v1/market/kline",
headers={"X-API-Key": os.environ.get("TICKDB_API_KEY")},
params={
"symbol": "AAPL.US",
"interval": "1d",
"limit": 500,
"adjust": "forward" # 前复权,这是回测的标准选择
},
timeout=(3.05, 10)
)
data = response.json()
if data.get("code") == 0:
klines = data["data"]
print(f"最近收盘价(前复权): {klines[-1]['close']}")
实战要点:回测用前复权。这是业界标准。选错了,你的收益曲线会被人为放大或缩小。
2.3 为什么不复权也有用
不复权的 K 线反映的是真实市场报价,包括跳空缺口。在分析某些事件(财报、收购)造成的真实价格冲击时,不复权数据反而更准确——它告诉你市场实际看到了什么价格。
但回到策略回测:永远用复权数据。
三、概念二:除息——你"免费得到"的东西,其实有代价
3.1 除息是什么
公司分红或派息时,会从股价里扣除相应金额。这天叫除息日(Ex-Dividend Date)。
假设 AAPL 股价 $180,公司宣布每股分红 $0.25。除息日当天开盘价理论上会是 $179.75。账面上你"得到"了 $0.25,实际上你的股票市值少了 $0.25——总资产没变。
除息不是"发钱",是"把你的钱从左口袋挪到右口袋"。 只不过左口袋(股价)减少了,右口袋(现金)增加了。
3.2 为什么这影响你的回测
如果你的策略是"均线金叉买入,死叉卖出",用不复权数据回测时会看到大量假的"跳空下跌"。这些不是市场真的跌了,而是除息造成的系统显示。
# 演示除息对价格的影响
def simulate_dividend_impact():
"""
场景:策略在除息日前一天收盘买入
第二天开盘价会因为除息而"低开"
"""
pre_dividend_price = 180.00 # 除息日前一天收盘价
dividend_per_share = 0.25 # 每股分红
# 不复权:第二天开盘直接看到缺口
raw_open_next_day = pre_dividend_price - dividend_per_share # 179.75
apparent_drop = (raw_open_next_day - pre_dividend_price) / pre_dividend_price
print(f"不复权视角:下跌 {apparent_drop*100:.3f}%") # -0.139%
# 前复权:价格被向上调整,缺口被消除
# 此时你看到的"下跌"才是真正的市场波动
adjusted_open = raw_open_next_day # 前复权后价格已对齐
real_drop = (adjusted_open - pre_dividend_price) / pre_dividend_price
print(f"前复权视角:下跌 {real_drop*100:.3f}%")
return {
"raw_scenario": apparent_drop * 100,
"adjusted_scenario": real_drop * 100
}
simulate_dividend_impact()
# 输出:
# 不复权视角:下跌 -0.139%
# 前复权视角:下跌 0.000%(实际上没有下跌)
3.3 一个更隐蔽的坑:股息再投入
很多量化策略会假设"分红是额外收益"。但如果你不做股息再投入,分红后你的总资产几乎不变——除了税务损耗。
回测系统如果把分红简单加到收益里,会高估策略的收益率。正确的做法是:要么把分红视为现金保留(不计入收益),要么模拟股息再投入并使用前复权价格。
四、概念三:做空——高维世界的"镜像操作"
4.1 从"买入-持有"到"双向操作"
程序员学做空,最容易的类比是指针和反指针。
买入(Long):看涨 → 低价买,高价卖 → 利润 = 卖出价 - 买入价
做空(Short):看跌 → 高价借,低价还 → 利润 = 借出价 - 归还价
做空本质是借券卖出。你向券商借股票,当前价格(比如 $100)卖出,等股价跌到 $80 再买回来还回去。利润就是 $20。
4.2 做空的三个"程序员式"理解
① 收益曲线是镜像的,但不是对称的
Long 盈亏曲线: Short 盈亏曲线:
/ \
/ \
--o------- ---o--------
买入价 借出价
区别在于:做空的潜在亏损是无限的。股价可以跌到 0(你最多赚 100%),但理论上可以涨到无穷(你需要无限资金来补仓)。
② 保证金机制——内存溢出风险
做空需要保证金,就像程序运行需要内存。股价涨了,你需要追加保证金(Margin Call)。如果保证金不够,券商强制平仓(Force Liquidation)——不管你愿不愿意,系统直接帮你买回来还券。
# 简化的做空保证金计算
class ShortPosition:
def __init__(self, entry_price, shares, margin_ratio=0.5):
self.entry_price = entry_price
self.shares = shares
self.initial_margin = entry_price * shares * margin_ratio
# 维持保证金率,跌破后触发 Margin Call
self.maintenance_margin_ratio = 0.25
def current_value(self, current_price):
"""空头持仓市值(负数表示卖出获得现金)"""
return current_price * self.shares
def margin_ratio(self, current_price):
"""当前保证金率"""
equity = self.initial_margin + (self.entry_price - current_price) * self.shares
liability = current_price * self.shares
return equity / liability if liability > 0 else float('inf')
def check_margin_call(self, current_price):
ratio = self.margin_ratio(current_price)
if ratio < self.maintenance_margin_ratio:
print(f"⚠️ MARGIN CALL! 当前保证金率 {ratio:.2%} < 维持率 {self.maintenance_margin_ratio:.2%}")
print(f" 需要补足至 ${self.initial_margin * self.maintenance_margin_ratio:.2f}")
return ratio >= self.maintenance_margin_ratio
# 测试场景
pos = ShortPosition(entry_price=100, shares=100, margin_ratio=0.5)
print(f"初始保证金: ${pos.initial_margin:.2f}")
# 股价涨到 150,保证金率危险
ratio = pos.check_margin_call(current_price=150)
print(f"股价 150 时保证金率: {ratio:.2%}\n")
# ⚠️ MARGIN CALL! 当前保证金率 16.67% < 维持率 25.00%
③ 卖空费用(Short Interest Rate)——持续的内存占用费
做空借券不是免费的。券商会收取年化利息,通常在 0.25% - 50% 不等。热门做空标的(比如 GameStop 这种散户逼空股)做空利率可以飙升到三位数。
回测系统如果忽略做空成本,会严重高估做空策略的夏普比率。
4.3 什么时候必须懂做空
- 统计套利(多空配对交易):同时做多低估标的、做空高估标的
- 事件驱动(做空财务造假公司):参考浑水(Muddy Waters)模式
- 对冲(保护多头头寸):下跌市场中减少损失
五、概念四:期权——给股票买"保险",还是给自己买"彩票"
5.1 期权的两个维度
期权(Option)有两个核心维度,每个维度有两种状态:
维度一:方向
- 看涨期权(Call):有权在约定价格买股票
- 看跌期权(Put):有权在约定价格卖股票
维度二:角色
- 买方(Holder):付出权利金,获得权利(理论上收益无限、损失有限)
- 卖方(Writer):收取权利金,承担义务(理论上收益有限、损失无限)
四个基本组合:
| 组合 | 预期方向 | 风险特征 |
|---|---|---|
| 买入 Call | 大涨 | 损失上限为权利金 |
| 买入 Put | 大跌 | 损失上限为权利金 |
| 卖出 Call | 不涨或小跌 | 潜在损失无限 |
| 卖出 Put | 不跌或小涨 | 潜在损失接近无限 |
5.2 为什么程序员要理解期权
不是因为你要"炒期权"。而是因为期权数据里藏着市场对未来价格的预期。
隐含波动率(IV) 是期权定价反推出来的市场波动率预期。这是量化交易中最重要的情绪指标之一。
# 演示期权链的基本结构(概念层,不含实际定价模型)
def parse_option_chain(ticker, expiry_date, underlying_price=100):
"""
期权链:同一到期日的所有 Call 和 Put 合约
Strike Price(行权价):约定的买卖价格
"""
strikes = [85, 90, 95, 100, 105, 110, 115] # 行权价序列
option_chain = {
"symbol": ticker,
"expiry": expiry_date,
"underlying_price": underlying_price,
"calls": {},
"puts": {}
}
for strike in strikes:
# ATM(平值):行权价 ≈ 当前价
moneyness = "ITM" if strike < underlying_price else ("OTM" if strike > underlying_price else "ATM")
option_chain["calls"][strike] = {
"moneyness": moneyness,
"type": "call"
}
option_chain["puts"][strike] = {
"moneyness": moneyness,
"type": "put"
}
return option_chain
chain = parse_option_chain("AAPL", "2026-04-18", underlying_price=180.25)
print("=== AAPL 期权链(部分)===")
print(f"标的现价: ${chain['underlying_price']}")
print(f"到期日: {chain['expiry']}\n")
for strike, info in chain["puts"].items():
print(f" Put ${strike} [{info['moneyness']}]")
print(" ───── ATM ─────")
for strike, info in chain["calls"].items():
print(f" Call ${strike} [{info['moneyness']}]")
5.3 一个期权数据的实际应用
期权 Skew(波动率偏斜)反映市场对未来下跌的恐惧程度。当 Skew 急剧上升时,往往意味着市场正在定价"黑天鹅"风险——这本身就是量化信号。
理解期权,不需要你会定价,但需要你知道它代表什么。
六、概念五:期货——带杠杆的"未来之约"
6.1 期货的本质
期货(Futures)本质上是一个标准化合约:约定在未来的某个时间、以某个价格、交割某种资产。
你不需要"买"期货,你需要"开仓"——承诺在到期时履行交割义务(或在到期前平仓了结)。
期货的三大核心机制:
| 机制 | 含义 | 程序员类比 |
|---|---|---|
| 杠杆(Leverage) | 只需缴纳保证金(约 5-10%)就能控制 100% 的合约价值 | 保证金交易 = 用 1 万本金撬动 10 万仓位 |
| 每日结算(Mark to Market) | 每天根据结算价计算盈亏,直接从保证金账户扣除 | 日终刷新账户余额 |
| 到期交割/平仓 | 可以实物交割,也可以到期前对冲平仓 | close() 方法释放仓位 |
6.2 期货价格的"预期陷阱"
期货价格 ≠ 现货价格。
期货价格 = 现货价格 + 持有成本(Carry Cost)- 持有收益(Convenience Yield)
这叫持有成本模型(Cost of Carry Model)。
def futures_price(spot_price, risk_free_rate, time_to_expiry, dividend_yield=0):
"""
期货定价基准公式
F = S × e^((r - q) × T)
其中:
S = 现货价格
r = 无风险利率
q = 股息收益率(持有股票时相当于"减分")
T = 到期时间(年)
"""
import math
F = spot_price * math.exp((risk_free_rate - dividend_yield) * time_to_expiry)
return F
# 示例:原油期货
# WTI 原油现货 $80/桶,3 个月期货
spot = 80.0
rate = 0.0525 # 当前美国国债利率约 5.25%
expiry = 0.25 # 3 个月 = 0.25 年
fair_futures = futures_price(spot, rate, expiry)
print(f"原油现货: ${spot}")
print(f"3 个月期货理论价: ${fair_futures:.2f}")
print(f"基差: ${fair_futures - spot:.2f}")
# 输出:期货升水约 $1.04,反映资金成本
6.3 期货在量化中的应用
- 商品期货:农产品、金属、能源——策略层面的"CTA"策略主战场
- 指数期货:IF(沪深 300)、IC(中证 500)——对冲工具
- 加密货币期货:永续合约——24/7 运行,高杠杆,需要特别注意资金费率(Funding Rate)
七、这 5 个概念怎么用:程序员实用地图
理解了概念之后,更重要的是知道在代码的哪个位置遇到它们。
量化系统开发阶段
│
├── 数据获取 ───────────→ 复权(adjust 参数选 forward/backward)
│
├── 因子计算 ───────────→ 除息(分红数据要从事件表里对齐)
│
├── 策略信号 ───────────→ 做空(双向仓位的保证金计算)
│ 期权 IV(作为市场情绪因子输入)
│
├── 持仓管理 ───────────→ 期货保证金(维持保证金率、强制平仓线)
│
└── 业绩归因 ───────────→ 全流程(所有概念综合影响最终收益)
7.1 这 5 个概念的掌握优先级
| 优先级 | 概念 | 理由 |
|---|---|---|
| ⭐⭐⭐ 必懂 | 复权 | 不懂这个,你的回测数字全是错的 |
| ⭐⭐⭐ 必懂 | 除息 | 与复权配合,影响价格数据的准确性 |
| ⭐⭐ 建议懂 | 做空 | 不做空策略可以先跳过,但理解逻辑有助于看懂市场结构 |
| ⭐⭐ 建议懂 | 期权 | 理解 IV 和 Skew 即可,不需要会定价 |
| ⭐ 了解 | 期货 | 有相关策略需求时再深入 |
优先级判断标准:如果这个概念你没懂,你的回测结果会不会系统性错误?
- 复权和除息:会。这两个直接决定你用的价格数据对不对。
- 做空:看策略。纯多策略不需要,统计套利必须懂。
- 期权和期货:看方向。商品 CTA 策略绕不开,股票多因子可以先跳过。
八、结语
回到开头那个场景:你回测跑出来 47% 收益,早上醒来变成 12%。
问题大概率不在代码里,而在数据里。用了不复权的价格,除息没对齐,期货保证金算错了——任何一条都足够让你的"高夏普"变成"高风险"。
程序员最大的优势是调试能力。代码有 BUG,trace 一下;回测有 BUG,先从这 5 个概念查起。
你懂怎么写没有 BUG 的代码。现在,学会读没有 BUG 的数据。
下一步行动
如果你是刚转量化的程序员,建议按这个顺序建立你的金融知识体系:
- 复权和除息(两天内搞定,数据层面立刻受益)
- 用 TickDB 的前复权 K 线数据重新跑一遍你的回测——你可能会发现结果大变
- 理解做空的保证金机制(避免爆仓的第一课)
- 了解期权 IV 作为市场情绪信号的含义
如果你已经有策略在跑,用 TickDB 的 adjust=forward 参数重新回测一次,比你优化任何因子参数的收益都大。
获取 TickDB API Key:访问 tickdb.ai,注册后可在控制台一键生成。Python 示例代码参考上方代码块,直接复制运行即可。
风险提示:本文不构成任何投资建议。量化策略存在市场风险,过往回测结果不代表未来收益。期货、期权等衍生品具有高杠杆特性,可能导致本金损失。投资有风险,决策需谨慎。