做日内策略、网格、资金流监控的开发者,几乎都卡过同一个问题:想要的那个分钟周期,数据源偏偏不给你。
Tushare 的 1/5/15/30/60 分钟线要 2000+ 积分、且每月再付 ¥1000;AKShare 的分钟线又常出现「时间乱码、价格跳变、老断连」(CSDN 文库问答原文)。与其为某个特定周期反复氪金、或在坑里打转,不如换个思路------只要拿到 1 分钟线,任意周期 Bar 都能本地合成 。本期就用魔码纯 HTTP 接口讲清这件事,并重点讲透一个踩坑无数的细节:先复权,后聚合。
本文你将得到什么
- 一个零依赖的
synthesize_bars()聚合器:把 1 分钟线合成 5/15/30/60 分钟任意周期,开=首、收=尾、高=max、低=min、量=sum。 - 一个被很多人忽略的致命坑:除权发生在某根 Bar 内部时,不复权直接聚合会产生虚假跳变,附实测对比。
- 魔码日线接口的真实字段验证(
sf复权标记),说明如何用统一口径做「先复权后聚合」。
一、为什么自己合成更省心
数据源对分钟线的「按需供给」往往和你的策略周期对不上:你做 15 分钟网格,它只给 1 分钟和日线;你做 30 分钟资金流,它要你单独开权限。商业行情 API(如魔码 Pro)通常提供 1 分钟线这一最细粒度,剩下的一切周期都可以在本地用几行代码拼出来------一次接入,全周期自由。
更重要的是口径统一。多源数据的「同一只票对不上」是量化圈经典坑(CSDN《个人做 A 股量化两年踩过的 6 个坑》):复权方式不同、除权时点不同、停牌是否补数、字段定义不同(有的 amount 是成交额有的却是成交量)。自己掌握聚合,意味着所有周期都从同一份 1 分钟基础数据派生,口径天然一致。
二、核心聚合器
聚合规则必须固定:一根目标周期 Bar 的开 取窗口内第一根 1 分钟的开 ,收 取最后一根的收 ,高/低 取窗口极值,量取求和。写错任何一条都会让合成 K 线失真。
python
# -*- coding: utf-8 -*-
import json, random, datetime
import requests
BASE = "https://api.momaapi.com"
LICENCE = "TEST-API-TOKEN-MOMA-836089C22111" # 演示证书,请换成你自己的正式证书
def aggregate(buf):
return {
"t": buf[0]["t"],
"o": buf[0]["o"],
"h": max(b["h"] for b in buf),
"l": min(b["l"] for b in buf),
"c": buf[-1]["c"],
"v": sum(b["v"] for b in buf),
}
def synthesize_bars(minutes, freq_min):
bars, buf = [], []
for m in minutes:
buf.append(m)
if len(buf) == freq_min:
bars.append(aggregate(buf)); buf = []
if buf:
bars.append(aggregate(buf)) # 尾部不足一个周期的残余,仍可聚合成一根
return bars
拿到 1 分钟线(魔码 Pro 的 1 分钟接口,演示证书无此权限,下文用本地确定性序列演示聚合逻辑),直接:
python
bars_5m = synthesize_bars(minutes_1m, 5)
bars_15m = synthesize_bars(minutes_1m, 15)
bars_60m = synthesize_bars(minutes_1m, 60)
三、构造一个交易日的 1 分钟线(合成示意)
演示证书无 1 分钟线权限,故下方用本地确定性随机构造一个交易日(09:30--11:30、13:00--15:00,共 240 根,跳过午休),仅用于演示聚合逻辑 ;正式环境把 minutes_1m 换成魔码 Pro 真实 1 分钟返回即可,聚合器无需改动。
python
def make_one_day_1min(seed=42, start="2026-03-02 09:30:00"):
rnd = random.Random(seed)
t = datetime.datetime.strptime(start, "%Y-%m-%d %H:%M:%S")
minutes, price = [], 100.0
for _ in range(240):
o = price
c = max(1.0, o + rnd.uniform(-0.15, 0.15))
h = max(o, c) + rnd.uniform(0, 0.1)
l = min(o, c) - rnd.uniform(0, 0.1)
v = rnd.randint(1000, 5000)
minutes.append({"t": t.strftime("%Y-%m-%d %H:%M:%S"),
"o": round(o, 2), "h": round(h, 2),
"l": round(l, 2), "c": round(c, 2), "v": v})
price = c
t += datetime.timedelta(minutes=1)
if t.hour == 11 and t.minute == 30:
t = t.replace(hour=13, minute=0)
return minutes
四、先复权,后聚合(本期最重要的坑)
很多人忽略一点:除权/送转可能发生在某根目标周期 Bar 的内部。如果直接拿未复权价格聚合,那根 Bar 的高/低/收会被「拆分前后的价格」撕裂,区间收益率完全失真。正确做法是先把 1 分钟线做前复权(把除权前价格缩放到最新尺度),再聚合。
python
def make_split_day(drop_at, drop_ratio, seed=7):
"""构造一个交易日,在 drop_at 处发生除权(价格台阶式下调 drop_ratio)。"""
rnd = random.Random(seed)
t = datetime.datetime(2026, 3, 2, 9, 30, 0)
mins, price = [], 100.0
for i in range(240):
if i == drop_at:
price = price * (1 - drop_ratio)
o = price
c = max(1.0, price + rnd.uniform(-0.15, 0.15))
h, l = max(o, c) + rnd.uniform(0, 0.08), min(o, c) - rnd.uniform(0, 0.08)
mins.append({"t": t.strftime("%Y-%m-%d %H:%M:%S"), "o": round(o, 2),
"h": round(h, 2), "l": round(l, 2), "c": round(c, 2),
"v": rnd.randint(1000, 5000)})
price = c
t += datetime.timedelta(minutes=1)
if t.hour == 11 and t.minute == 30:
t = t.replace(hour=13, minute=0)
return mins
def qfq_adjust(minutes, drop_at, drop_ratio):
"""前复权:除权前价格按 (1-drop_ratio) 缩放到最新(除权后)尺度,全序列同口径。"""
post_scale = 1 - drop_ratio
return [{**m, k: round(m[k] * (post_scale if i < drop_at else 1.0), 2)
for k in ("o", "h", "l", "c")} for i, m in enumerate(minutes)]
五、实测结果(魔码公开测试证书本地跑通)
[1] 1 分钟基础序列:240 根(09:30-15:00,含午休跳过)
首根 2026-03-02 09:30:00 开=100.0 / 末根 2026-03-02 14:59:00 收=99.51
[2] 合成各周期 Bar(聚合规则:开=首、收=尾、高=max、低=min、量=sum)
5分钟: 48 根 | 首开=100.0 末收=99.51 首高=100.05 量合计=725736
15分钟: 16 根 | 首开=100.0 末收=99.51 首高=100.05 量合计=725736
30分钟: 8 根 | 首开=100.0 末收=99.51 首高=100.05 量合计=725736
60分钟: 4 根 | 首开=100.0 末收=99.51 首高=100.13 量合计=725736
[3] 先复权后聚合(规避口径漂移)
未复权 第3根60分:开=98.13 高=98.19 低=78.18 收=78.33 区间收益=-20.18%
复权后 第3根60分:开=78.5 高=78.95 低=78.18 收=78.33 区间收益=-0.22%
→ 未复权在除权点出现虚假跳变(高/低被拆分价撕裂, 区间收益失真);复权后口径平滑一致
[4] 真实接口验证:魔码日线字段与复权标记 sf
600519 日线返回 50 行;字段=['a', 'c', 'h', 'l', 'o', 'pc', 'sf', 't', 'v']
末行 t=2025-12-01 收=11.69 成交额(a)=1205480322.0 复权标记(sf)=0.0
→ 利用 sf 先对价格做复权,再按本文学到的聚合器合成周/月线,口径一致
逐条解读:
- 1→2 周期合成正确:240 根 1 分钟 → 5 分钟 48 根、15 分钟 16 根、30 分钟 8 根、60 分钟 4 根,聚合断言(首 bar 开 = 首 1min 开、末 bar 收 = 末 1min 收、首高 = 窗口 max)全部通过。成交量合计在各周期恒定(725736),说明汇总无遗漏。
- 3 复权坑实测 :在盘中约 13:40(第 130 根 1 分钟)放一次 20% 除权。未复权的第 3 根 60 分钟 Bar 开 98.13、低 78.18、收 78.33,区间收益被算成 -20.18% ------这完全是除权撕裂造成的假象;复权后同一根 Bar 开 78.5、低 78.18、收 78.33,区间收益仅 -0.22% ,口径平滑。这个例子说明:任何跨除权日的聚合,务必先复权。
- 4 真实字段验证 :魔码日线返回统一的
t/o/h/l/c/v/a/pc/sf九字段,其中sf即复权标记------正好可以用来在本地做「先复权后聚合」,从根上消灭多源口径漂移。
说明:演示证书无 1 分钟线权限,故 1--3 的 1 分钟基础序列为本地构造(已明确标注);聚合与复权逻辑均已通过断言验证。正式环境替换为魔码 Pro 真实 1 分钟返回即可,聚合器不变。
六、常见坑与提示
- 别拿未复权价直接聚合成周/月线:除权日若落在某根周线内部,会产生和 3 一样的虚假跳变,因子与收益率全错。
- 尾根不足周期的处理:一天最后一段 1 分钟不足一个目标周期时(如收盘前 3 分钟),本实现仍聚合成一根;实盘回测通常丢弃或并入下一交易日,按策略约定处理即可。
- 时间对齐:1 分钟序列必须保证连续、不重复、含正确的交易时段(跳过午休与停牌),否则聚合窗口错位。
- 遵守频控 :批量拉取 1 分钟线请在限速之内,魔码套餐带「次/分钟」频率限制,接口
rate_limit字段可查实时余量。
小结与下篇预告
本期我们用不到一百行代码,把 1 分钟线合成出任意周期 K 线,并验证了最关键的工程细节------先复权,后聚合 ,避免除权撕裂导致的虚假区间收益。这让「为某个分钟周期单独氪金」和「AKShare 分钟线错乱」两大痛点迎刃而解:拿到魔码 Pro 的 1 分钟线,全周期自由派生。
下一篇(#88)我们把视角拉回工程链路:如何把前面这些步骤(基线指纹、多源对账、新鲜度、缓存)串成一条可在 CI 里门禁的可复现流水线,让每次数据更新都自动过质检。