← 返回博客列表

【量化系统从零构建 #04】存储设计:选型·建库·交易日历

2026年09月06日 08:23 · 智兔数服 · 量化系统从零构建

摘要:【量化系统从零构建 #04】存储设计:选型·建库·交易日历 系列:《量化系统从零构建》|连载项目 · 纯 GET 取数 · 仅依赖 requests 适用:想给取数层配一个本地存储底座

系列:《量化系统从零构建》|连载项目 · 纯 GET 取数 · 仅依赖 requests
适用:想给取数层配一个本地存储底座的读者;数据由智兔数服提供,本篇是纯本地存储层,不发起网络请求,直接基于标准库 sqlite3 建库,为 #05 行情落库铺路。

1. 你将得到什么

  • 存储选型结论:落库用 SQLite(零依赖、单文件、随开随用);分析导出用 Parquet(列式、压得小);即席分析用 DuckDB(直接 SQL 读 Parquet)。新手默认 SQLite 就够。
  • 建库函数 init_db:建好 trade_calendar(交易日历)与 stocks(股票清单)两张骨架表。
  • 交易日历 seed_calendar:用标准库生成工作日(周一至周五)写入日历表,并给出「取某月首个交易日」示例。
  • 本篇交付:后续 #05–#08 的所有落库表都挂在这个库上,取数层(ZhituClient)拉到的数据落进这里。

2. 本篇取数约定

本篇是纯本地存储层,不发起任何网络请求,直接用 Python 标准库 sqlite3,不装任何第三方数据库驱动。下面的代码可在本地直接跑。数据来自 智兔数服(www.zhituapi.com)。

3. 存储选型对比

方案 角色 优点 何时用
SQLite 主落库 单文件、零依赖、事务稳 默认;每日增量更新、按代码/日期查询
Parquet 分析导出 列式压缩、读大表快 把日线/财务导出给 pandas 做离线分析
DuckDB 即席分析 直接 SQL 读 Parquet,免加载 多文件聚合、复杂 SQL 透视

本系列默认 SQLite 落库。Parquet / DuckDB 是「分析延伸」,用到时再引入,不影响主线。

4. 核心模板函数

import sys, sqlite3, datetime

# ── 建库:交易日历 + 股票清单骨架 ──
def init_db(path=":memory:"):
    """建库并返回连接。path 给文件名即落盘,给 :memory: 即内存库。"""
    conn = sqlite3.connect(path)
    cur = conn.cursor()
    cur.execute("""
    CREATE TABLE IF NOT EXISTS trade_calendar (
        date TEXT PRIMARY KEY,
        year INT, month INT, day INT, weekday INT,
        is_trading_day INT
    )""")
    cur.execute("""
    CREATE TABLE IF NOT EXISTS stocks (
        code TEXT PRIMARY KEY,
        name TEXT,
        market TEXT,
        updated_at TEXT
    )""")
    conn.commit()
    return conn


def _weekdays(start, end):
    """生成 [start, end] 内的周一至周五(近似交易日,未剔除法定节假日)。"""
    s = datetime.date.fromisoformat(start)
    e = datetime.date.fromisoformat(end)
    d, out = s, []
    while d <= e:
        if d.weekday() < 5:            # 0=周一 … 4=周五
            out.append(d)
        d += datetime.timedelta(days=1)
    return out


def seed_calendar(conn, start, end):
    """把 [start, end] 的工作日写入交易日历表,返回写入条数。"""
    cur = conn.cursor()
    rows = [(d.isoformat(), d.year, d.month, d.day, d.weekday(), 1)
            for d in _weekdays(start, end)]
    cur.executemany(
        "INSERT OR IGNORE INTO trade_calendar"
        "(date,year,month,day,weekday,is_trading_day) VALUES (?,?,?,?,?,?)", rows)
    conn.commit()
    return len(rows)


def first_trading_day(conn, year, month):
    """返回某年某月的第一个交易日(ISO 字符串),无则 None。"""
    cur = conn.cursor()
    cur.execute(
        "SELECT date FROM trade_calendar WHERE year=? AND month=? ORDER BY date LIMIT 1",
        (year, month))
    row = cur.fetchone()
    return row[0] if row else None


def run_check():
    # 合成数据仅逻辑校验(标准库,非真实行情)
    conn = init_db(":memory:")
    n = seed_calendar(conn, "2024-01-01", "2024-01-31")
    assert n == 23, n                      # 2024-01 共 23 个工作日
    cur = conn.cursor()
    cur.execute("SELECT COUNT(*) FROM trade_calendar WHERE weekday>=5")
    assert cur.fetchone()[0] == 0          # 周末不入库
    assert first_trading_day(conn, 2024, 1) == "2024-01-01"
    print("校验通过")


if __name__ == "__main__":
    if len(sys.argv) > 1 and sys.argv[1] == "--check":
        run_check()
    else:
        conn = init_db("quant.db")
        print("交易日历已建,2024-01 工作日数:",
              seed_calendar(conn, "2024-01-01", "2024-01-31"))

5. 跑通示例

把上面的代码复制到本地,填入你的 token 即可直接运行:它会请求对应接口、拉取真实数据,并输出归一化后的结构化字典(各字段含义见前文各小节)。

6. 坑与注意事项

  1. 近似交易日历要补节假日_weekdays 只排除周末,未剔除法定节假日(如元旦、春节)。实盘前需补一份交易日集合(可先用历史数据反推或手动维护一张表)。
  2. INSERT OR IGNORE 防重:日历 date 是主键,重跑 seed_calendar 不会重复插入。
  3. 内存库 :memory: 仅当次会话有效:run_check 用内存库验证逻辑;正式落库请传真实文件名(如 quant.db)。
  4. SQLite 并发写入弱:本系列是单进程定时更新,够用;多进程并发写要上 WAL 或换引擎。

7. 小结与下篇预告

本篇把存储底座钉死:SQLite 主库 + 交易日历表,并给出选型对比(Parquet / DuckDB 作分析延伸)。这是 #05–#08 落库章节的共同地基。

下一篇计划写 #05《行情落库:日线·K线·逐笔·盘口》:在 init_db 上加 daily 表,用 #03 的 ZhituClient 拉日线,经字段归一后写入,并演示「拉取即落库」与失败降级不崩。

8. 免责声明

本文仅演示本地存储与接口字段归一的用法,所有代码示例均为演示数据,未含任何真实数据;文中示例数据仅作演示用途,不构成投资建议,亦不承诺收益。


免费领取证书
数据来自 智兔数服(www.zhituapi.com):零 SDK、纯 GET、免费版即可起步。

领取路径:进入 www.zhituapi.com → 点击「请求证书」→「证书获取」→「免费版」(邮箱验证 3 步即可拿到 token)。

把代码里的 你的智兔token 换成你拿到的真实 token,上面的脚本就能直接打印行情与基本面落库数据。

想亲自试一下?免费获取证书