第 8 章 · 01 双模式设计:日常增量与历史回填 本节摘要:本节讲 Sequoia-X 主程序入口的第一个关键设计——双模式。日常模式( )和回填模式( )走完全不同的代码路径:日常模式追求「快」(23 分钟跑完全市场),回填模式追求「稳」(12 分钟一次性灌全市场)。它们共用配置层、数据引擎、策略库,但入口分流、行为差异。本节会先讲「为什么需要两种模式」(不同的时间预算与容错要求),再讲 argparse 模式分发(一个标志位控制两条路径),然后讲两条路径的工作流对比(回填走单进程重试、日常走多进程并行),最后讲「crontab 自动运行」——Sequoia-X 的日常模式可以挂到系统定时任务,收盘后自动跑。 内容来源:原项目主程序入口精读。
本节摘要:本节讲 Sequoia-X 主程序入口的第一个关键设计——双模式。日常模式(
python <主程序入口>)和回填模式(python <主程序入口> --backfill)走完全不同的代码路径:日常模式追求「快」(2~3 分钟跑完全市场),回填模式追求「稳」(12 分钟一次性灌全市场)。它们共用配置层、数据引擎、策略库,但入口分流、行为差异。本节会先讲「为什么需要两种模式」(不同的时间预算与容错要求),再讲 argparse 模式分发(一个标志位控制两条路径),然后讲两条路径的工作流对比(回填走单进程重试、日常走多进程并行),最后讲「crontab 自动运行」——Sequoia-X 的日常模式可以挂到系统定时任务,收盘后自动跑。
内容来源:原项目主程序入口精读。
💡 核心心法:好的「主流程」设计——一条入口、两种模式、各自优化。把「互斥的场景」分到不同模式,不试图用一个模式做所有事。
阅读完本节,你应当能够:
回看第 2 章讲的「冷启动三步」:装环境 → 配配置 → 灌数据。灌数据和「每天用」其实是两件时间预算与容错要求完全不同的事:
| 维度 | 日常模式 | 回填模式 |
|---|---|---|
| 触发时机 | 每个交易日收盘后 | 首次部署 / 数据修复 |
| 数据规模 | 每只股票 1~2 根 K 线 | 每只股票 数百根 K 线 |
| 时间预算 | 2~3 分钟内必须结束 | 十几分钟 |
| 容错要求 | 快速完成,单股失败可丢 | 每只都不能漏,必须重试 |
| 进程模型 | 8 进程并行(追求速度) | 单进程 + 失败重试(追求稳) |
| 中断处理 | 重跑从「最后日期」续 | 重跑从「未完成股票」续 |
为什么不能合并成一种模式?
| 合并的代价 | 后果 |
|---|---|
| 用回填模式跑日常 | 12 分钟才出结果——没人能接受 |
| 用日常模式跑首次 | 5000 只股票要 5000 次单股查询——几小时 |
| 用同一进程模型 | 日常需要的并行加速与回填需要的失败重试逻辑相互干扰 |
「双模式」的本质是「风险特征不同 → 工程取舍不同」——Sequoia-X 用「两条代码路径」让两种场景各自优化。
Sequoia-X 用 Python 标准库 argparse 实现命令行参数解析:
import argparse def main() -> None: parser = argparse.ArgumentParser(description="Sequoia-X V2 选股系统") parser.add_argument( "--backfill", action="store_true", help="回填模式:通过 baostock 拉取全市场历史 K 线(约 12 分钟)", ) args = parser.parse_args() if args.backfill: # 回填模式 ... return # 日常模式 ...
action="store_true" 的精妙parser.add_argument("--backfill", action="store_true", help="...")
--backfill → args.backfill = False → 走日常模式;--backfill → args.backfill = True → 走回填模式。action="store_true" 是 Python argparse 的「布尔开关」惯用法——不传为 False,传了为 True——一个标志位控制两条路径。
if ... return 早返模式if args.backfill: # 回填逻辑 return # ← 关键:早返 # 日常逻辑(继续往下走)
「早返」让两种模式互不干扰——回填模式 return 后,日常模式的代码不会被执行——避免大量 if/else 嵌套。
日常模式:python <主程序入口> ───────────────────────────────────── 1. 初始化配置 + 日志 2. 初始化数据引擎(如果 SQLite 不存在则报错) 3. 拉增量 K 线(8 进程并行,约 2 分钟) 4. 实例化 7 个策略 5. 遍历策略 → 选股结果 6. 每个策略结果推送到对应飞书机器人 7. 结束
关键:从「拉数据」到「推送」是一条完整闭环——跑完即结束。
回填模式:python <主程序入口> --backfill ───────────────────────────────────── 1. 初始化配置 + 日志 2. 初始化数据引擎 3. 拉全市场股票列表 4. 单进程遍历每只股票 ├─ 查 SQLite 该股最后日期 ├─ 已是最新 → 跳过(断点续传) ├─ 需要补数据 → 拉历史 K 线 ├─ 失败重试 3 次(2s/4s/8s) └─ 每 200 只重连 baostock 5. 结束
关键:回填模式不跑策略、不推送飞书——只灌数据。
| 维度 | 日常模式 | 回填模式 |
|---|---|---|
| 拉数据范围 | 增量(最后日期+1 → 今天) | 全市场(起始日期 → 今天) |
| 进程数 | 8 | 1 |
| 失败处理 | 仅记日志 + 跳过 | 重试 3 次 + 指数退避 |
| 是否跑策略 | ✅ | ❌ |
| 是否推飞书 | ✅ | ❌ |
| 典型耗时 | 2~3 分钟 | 约 12 分钟 |
| 典型场景 | 每个交易日收盘后 | 首次部署 / 数据修复 |
「是否跑策略」「是否推飞书」这两个问题,让两种模式几乎没有代码复用——它们是两套独立的逻辑,仅共享配置和数据库。
# 1. 装环境 uv sync # 2. 配配置 cp <环境变量模板> <本地配置> # 填入飞书 Webhook # 编辑本地配置,填入 FEISHU_WEBHOOK_URL # 3. 回填一次(首次灌入全市场历史) python <主程序入口> --backfill # 约 12 分钟 # 4. 日常运行(每天收盘后跑一次) python <主程序入口> # 约 2~3 分钟
# 每天 19:00 跑(A股收盘 15:00,缓冲 4 小时) 0 19 * * 1-5 cd <项目根目录> && <虚拟环境>/python <主程序入口> >> <日志文件> 2>&1
4 小时缓冲是为了让 baostock 完成数据汇总——
--backfill模式更适合非交易时段跑(避开数据未就绪)。
# 假设某天拉数据失败,需要重拉 python <主程序入口> --backfill # 12 分钟,会跳过已入库的
backfill模式天然支持断点续传——只补缺失部分。
下面这段代码完整演示 Sequoia-X 的「argparse 模式分发」教学版。它不实际拉数据,只演示「一个标志位控制两条路径」:
# mode_dispatch_demo.py # 可运行:argparse 模式分发演示 import argparse import time def run_daily_mode() -> str: """日常模式:拉增量 → 跑策略 → 推飞书(教学模拟)。""" print("=" * 50) print("【日常模式】") print("=" * 50) print(" 1. 拉增量 K 线(8 进程并行,模拟 30s)") time.sleep(0.3) print(" 2. 实例化 7 个策略") print(" 3. 遍历策略 → 选股结果") print(" - 海龟突破 → 3 只") print(" - 均线金叉 → 5 只") print(" - RPS → 8 只") print(" - ...") print(" 4. 推送飞书(共 7 个机器人)") return "daily_done" def run_backfill_mode() -> str: """回填模式:单进程拉全市场历史(教学模拟)。""" print("=" * 50) print("【回填模式】") print("=" * 50) print(" 1. 拉全市场股票列表(5200 只)") print(" 2. 单进程遍历每只股票") print(" - 已是最新 → 跳过(断点续传)") print(" - 需要补数据 → 拉历史 K 线") print(" - 失败重试 3 次") print(" 3. 不跑策略、不推飞书") return "backfill_done" def main(): parser = argparse.ArgumentParser(description="Sequoia-X V2 选股系统(教学版)") parser.add_argument( "--backfill", action="store_true", help="回填模式:拉全市场历史 K 线(约 12 分钟)", ) args = parser.parse_args() # 早返模式分叉 if args.backfill: print(run_backfill_mode()) return # 日常模式 print(run_daily_mode()) if __name__ == "__main__": main()
测试方式:
# 不传参数 → 日常模式 python mode_dispatch_demo.py # 传 --backfill → 回填模式 python mode_dispatch_demo.py --backfill # 看 help python mode_dispatch_demo.py --help
预期输出(日常模式):
================================================== 【日常模式】 ================================================== 1. 拉增量 K 线(8 进程并行,模拟 30s) 2. 实例化 7 个策略 3. 遍历策略 → 选股结果 - 海龟突破 → 3 只 - 均线金叉 → 5 只 - RPS → 8 只 - ... 4. 推送飞书(共 7 个机器人) daily_done
预期输出(回填模式):
================================================== 【回填模式】 ================================================== 1. 拉全市场股票列表(5200 只) 2. 单进程遍历每只股票 - 已是最新 → 跳过(断点续传) - 需要补数据 → 拉历史 K 线 - 失败重试 3 次 3. 不跑策略、不推飞书 backfill_done
这段代码清晰展示「一个
--backfill标志位控制两条完全不同的代码路径」的精妙结构。
Sequoia-X 真实的主程序入口核心结构(简化):
def main() -> None: parser = argparse.ArgumentParser(description="Sequoia-X V2 选股系统") parser.add_argument("--backfill", action="store_true", help="...") args = parser.parse_args() try: # 1. 初始化 settings = get_settings() logger = get_logger(__name__) engine = DataEngine(settings) # 2. 模式分叉 if args.backfill: # 回填模式 all_symbols = engine.get_all_symbols() engine.backfill(all_symbols) return # 3. 日常模式 engine.sync_today_bulk() # 拉增量 # 4. 注册策略 strategies = [ MaVolumeStrategy(engine, settings), TurtleTradeStrategy(engine, settings), # ... 共 7 个 ] # 5. 推送器 notifier = FeishuNotifier(settings) # 6. 遍历执行 + 推送 for strategy in strategies: try: selected = strategy.run() if selected: notifier.send(selected, ..., webhook_key=strategy.webhook_key) except Exception: logger.exception(...) # 异常隔离 except Exception: # 主流程兜底 logger.exception("主流程异常") sys.exit(1) # 非零退出码
结构非常清晰:
整个主程序入口不到 100 行——这是「好主流程」的范本:职责清晰、不嵌套、易读。
新手常犯的错是把「业务逻辑」写在主程序里——结果主程序入口 500+ 行,难以阅读、难以测试、难以维护。
Sequoia-X 的「短小主程序」哲学:
| 长主程序 | 短主程序 |
|---|---|
| 业务逻辑混在主流程 | 业务逻辑在子模块 |
| 一个文件改崩全盘 | 各自模块独立 |
| 测试要起整个主程序 | 子模块可独立测试 |
| 多人改同一文件 | 多人改不同文件 |
主程序入口应该只做「调度」——把每个动作「叫一下」就够了。
双模式设计 ✅ ← 本节 │ ▼ 下一节(第 02 节)策略执行与推送编排:日常模式的完整流程
action="store_true" 标志位 + 早返模式——一个参数控制两条路径。if ... return 早返:避免 if/else 嵌套——让两条路径各自独立。配套教学脚本:
images/mode_dispatch_demo.py——演示 argparse 模式分发。
下一节,把日常模式的「策略执行 + 推送编排」讲透——这是 Sequoia-X 「选股 + 触达」闭环的最后一环。