cover_image

基金套利,量化策略-Gemini 编程实战篇-4

东哥的平凡生活 静听烟雨任平生

从混杂到纯粹:基座数据库的“正名”与深度重构指南

我编写的这两个量化交易程序每天都在长大,周六早起,继续干,终于更新了ETF基金轮动套利的程序,发给了轮动群的小伙伴们:基金套利,跟着AI学轮动策略

让大伙儿周六也开始卷起来

你追我赶,组队一起走才不累,才有干劲


当然我也不能闲着,继续优化程序的系统架构。 我的两个程序就像两棵小树苗,春天到了,每一天都在成长。我这个园丁开始面临一个棘手的技术债:早期的系统架构和命名规范,已经承载不了日益膨胀的业务需求。

前两天,套利监控系统正在经历一次从“单体工具”到“工业级双模基座(arbcore)”的大重构。系统目前拥有两个独立的生态:

  1. LOFarb:主打 A 股 LOF 基金跟踪美股ETF的折价套利,我搞了一个"半自动交易群"。
  2. ETFRotate:主打 A 股 ETF 基金之间的轮动套利,又搞了一个“轮动交易群”。

在这个演进过程中,我发现了一个足以在未来引发灾难性 Bug 的隐患——底层数据库命名与概念的严重混淆。果然,我这个半路出家的编程门外汉,虽然有AI的加持,但是终究要露馅了。

今天就分享一下AI编程的强大,看看Gemini是如何给底层数据基座“把脉”,并制定全局“正名”与重构方案的。主要也是便于两个群里的伙伴们能更好地看懂我的程序,就容易进一步调试优化。


1. 痛点:被混淆的“ETF”与“LOF”

为了图方便,在早期的折价套利系统中,为了和无敌的网页保持一致,我将 3 个 A 股 ETF(159502、159518、513350)混入到了 LOFarb 程序的监控池中。这导致了一系列概念上的灾难:

  • 表名张冠李戴:这 3 个 ETF 的历史对账表,在数据库里面竟然被冠以了 lof_history_159502 的表名。
  • 同词不同义:系统底层广泛使用了“ETF”这个词,但它有时指代的是美股底层的资产(如 SPY、QQQ、XOP),有时指代的又是A股公募基金(如 159502)。

如果这种概念混淆延续到新重构的 arbcore 核心基座中,多线程读写与数据大一统极易引发张冠李戴的致命报错。

顺便说一个我也刚刚学的"基金代码"知识点:上海交易所的ETF主要是51开头,56和58是新增的,深交所的ETF是159开头;上交所的LOF是501开头,深交所是16开头。


2. 第一阶段:前端驱动的业务剥离与历史交接

为了解决这个问题,奉行“业务纯粹性”原则:让 LOFarb 专心只做 LOF 折价套利监控,把 A 股 ETF 彻底剥离给 ETFRotate 子系统。

步骤 1:前端一键阻断数据链路

没有选择去代码里暴力删减ETF基金,而是利用系统成熟的“配置驱动”架构,直接在 LOF00 网页端控制面板中删除了这 3 个混入的 ETF。 当点击配置保存的那一刻,系统的自动化链路完美衔接:

  • lof_config.yaml 配置文件自动剔除这几个基金。
  • 每日的数据爬虫(011)不再抓取它们的收盘价和净值。
  • 静态估值引擎(012)不再浪费算力推演它们。
  • 监控大屏自动让它们消失。

步骤 2:历史对账表的平滑交接

业务剥离后,数据库里还残留着这 3 个 ETF 过去的对账数据。既然要交给 ETFRotate 轮动系统去管,且未来也要给它们做静态估值对账表,我编写了一个独立 Python 脚本,先将 SQLite 中遗留的表名平滑过渡:

  • lof_history_159502 -> 重命名为 -> etf_history_159502
  • lof_history_159518 -> 重命名为 -> etf_history_159518
  • lof_history_513350 -> 重命名为 -> etf_history_513350

至此,历史资产交接完毕,LOFarb折价套利程序终于变得纯粹。


3. 第二阶段:工业级数据库命名规范的大摸底

更深层次的挑战在于数据库"底层大一统表"的规范化命名。经过全局统筹,敲定了以下的重构蓝图:

核心思想:A 股公募产品统称为 **fund**(囊括 LOF 和 A股 ETF),美股底层资产统称为 **usa_etf**。

把脉 lof_data 的“前世今生”

在改名之前,对核心表 lof_data 进行了深度剖析:

  • 它的真实身份:它是整个系统(包含 LOFarb 和 ETFRotate)的“A股基金基础成绩单”大一统表。存储 A 股代码、当天的收盘价 (price)、官方净值 (nav) 等。
  • 谁在写入它:LOFarb 的 011 数据爬虫每天写入;同时,ETFRotate 独立基座也在复用这个表结构来存储轮动 ETF 的数据。

也就是“轮动群”和“折价群”的同学们用的是同一个数据库。

  • 谁在读取它static_valuation.py 静态计算器极度依赖它,需要读取表里的 nav 和 price 结合汇率去算历史对账单。

结论:将 lof_data 改名为 fund_data ,它装的不仅是 LOF,也装了 A 股 ETF。同理,etf_daily_prices 必须改名为 usa_etf_daily_prices 以防混淆。


4. 第三阶段:牵一发而动全身的重构实施清单

虽然确定了完美的命名规范,但这属于“换心脏”级别的手术。改动这两张基准表的名字,意味着我们需要同时修改整个 arbcore 体系的关联链路。

以下是AI梳理出的全局重构清单,这时候发现AI的确牛逼啊,难道按照“预测”(瞎猜)大数据模型运作的机器代码竟然有这么强大的本事?

我经常在编程中恍恍惚惚,我面对是AI机器?还是真的一个专业的程序员?

针对 etf_daily_prices -> usa_etf_daily_prices

  1. 数据库核心 (arbcore/database/db_manager.py):修改 init_db 建表语句,修改 upsert_etf_price 方法名为 upsert_usa_etf_price,更新 cleanup_old_data 中的数据清理语句。
  2. ETFRotate 独立基座 (ETFRotate/core/db_manager.py):同上同步修改,保持双基座一致。
  3. 数据更新中枢 (LOF011_daily_updater.py):抓取美股代码存库时,调用的 Upsert 方法名跟随更新。
  4. 静态估值算法 (arbcore/calculators/static_valuation.py):核心 SQL 读取语句更改:SELECT date, price FROM usa_etf_daily_prices WHERE symbol = ?
  5. 诊断程序 (LOF012_calculate_static_valuation.py):盘前诊断探针的 SQL 查询表名更改。

针对 lof_data -> fund_data

  1. 数据库核心 (arbcore/database/db_manager.py & ETFRotate/core/db_manager.py):修改建表语句,将 save_lof_data 改名为 save_fund_data,同步更新更新估值、读取最新价格等内部 SQL。
  2. 数据更新中枢 (LOF011_daily_updater.py):存净值、存收盘价的方法调用更新,以及防重写的 SQL 查询语句更新。
  3. 诊断程序 (LOF012_calculate_static_valuation.py):透视打印日志中的 SQL 更改为 SELECT * FROM fund_data
  4. 静态估值算法 (arbcore/calculators/static_valuation.py):基石表的 Outer Join SQL 更改为 SELECT date, price as close, nav FROM fund_data WHERE fund_code = ?

隐性重构点:历史表名称大一统

为了彻底打通逻辑,如果 A股基金统称 fund,那么静态估值脚本 static_valuation.py 最后生成的历史对账表,其建表逻辑也需要从:table_name = f"lof_history_{fund_code}"升级为:table_name = f"fund_history_{fund_code}"这样一来,无论是 LOF 还是 A股 ETF 传入计算器,底层逻辑都将完全互通,不再有任何代码上的违和感。


5. 总结

“命名是编程中最难的两件事之一。”

在量化交易系统走向复杂、庞大、多模态演进的过程中,数据表的名称绝不仅仅是一个代号,它是业务逻辑与架构边界的具象化体现

通过这次从“混杂”到“纯粹”的概念剥离与表结构深度梳理,底层 SQLite 数据库终于建立起了清晰的护城河。这不仅为后续的双模态(折价套利 + 轮动套利)同源推演打下了坚实的基础,也让这套系统真正具备了实际操作框架的整洁度。

感谢AI的大力加持!Gemini表现不错


继续滑动看下一个
静听烟雨任平生
向上滑动看下一个