python量化自定义周期 K 线合成

做日内策略、网格、资金流监控的开发者,几乎都卡过同一个问题:想要的那个分钟周期,数据源偏偏不给你。

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 里门禁的可复现流水线,让每次数据更新都自动过质检。



相关推荐
视觉小萌新2 小时前
AI Class 3 · Chapter 1:变量、字符串、list/dict、if/for(python学习-小白版-有经验可跳过)
python·学习·list
IT召唤狮2 小时前
开源企业级零信任项目Pangolin,零信任能挡住PHP核弹漏洞吗?
开发语言·开源·php
知已难寻2 小时前
03 · OpenStack的计算管理 Nova
运维·服务器·开发语言·云原生·云计算·openstack·开源软件
Rover Ramble2 小时前
C++ 用互斥锁实现读写锁
开发语言·c++
宇宙无敌暴龙战士6662 小时前
MySQL 8 库表操作学习笔记:SQL 基础、库表管理、约束与用户授权全流程
数据库·sql·mysql·学习笔记·后端开发
猿究院_夏燊3 小时前
MySQL 的四种隔离级别
java·数据库·mysql
薛定谔的猫-菜鸟程序员3 小时前
软件架构设计师开发资料大全(从入门到精通)
后端·python·系统架构·flask·架构设计师
say_fall3 小时前
【Linux系统编程】Ext 文件系统:磁盘与存储、文件与目录、现代计算机究竟如何寻找文件
linux·运维·服务器·数据库·c++
码流子3 小时前
移动收费怎么做?手持终端+App+收费中心的架构实践
java·开发语言·架构