Sequoia-X 项目研究站 · 日常运行
Sequoia-X 日常运行:补增量、跑策略、推飞书的一次闭环
装好之后的每个交易日,你只需要执行一次 python main.py。它会先读本地库里已有的股票清单、把当日缺失的日 K 按股票切成最多八份并行补回来,然后依次跑七个形态策略,只有命中的策略才会把结果推到飞书。整个过程由 rich 输出带时间戳的日志,最后用 crontab 固定在收盘后执行。
Sequoia-X 日常模式有哪五步?
步骤与顺序来自 main.py 的日常分支,不是推测。
读本地股票清单
从 SQLite 里
SELECT symbol, MAX(date) FROM stock_daily GROUP BY symbol取出每只股票的最新日期。若库里一只股票都没有,日志会提示「本地无股票数据,请先执行 --backfill」并直接返回——这是首次运行的正常提示。算出需要补哪些股票
只对「最新日期早于今天」的股票生成任务;已经是最新的会被跳过。所有股票都是最新时,日志输出「所有股票已是最新,无需更新」,不做任何网络请求。
八进程并行拉当日增量
任务按
tasks[i::n]切成min(8, 任务数)份,每个子进程自己bs.login()独立拉取,避免单连接串行等待。子进程返回的行在父进程汇总成 DataFrame。清洗后写回 SQLite
数值列转数字、丢掉收盘价为空的记录、过滤
volume <= 0的停牌行;写入前先DELETE FROM stock_daily WHERE date = ?,再按批量 append,保证同一天重复执行不会产生重复行。跑七个策略并按需推送
策略在
main.py的列表里顺序执行,每个策略读同一份本地数据、返回命中的股票代码;命中为空只记日志,命中的才调用飞书推送器并带上该策略的webhook_key。
--backfill),它在安装与回填页里说明。两者不要混着用:日常模式为了补当日缺口会删掉当日记录再写,回填模式则是逐只增量追加。Sequoia-X 的八进程增量做了什么
把并发、去重与容错拆开看,出问题时才知道该查哪一环。
| 环节 | 源码行为 | 适用场景 | 注意点 |
|---|---|---|---|
| 进程数 | n_workers = min(8, len(tasks)) | 全市场日常更新 | 待补股票少于 8 只时进程数自动减少,不会空跑 |
| 任务分片 | chunks = [tasks[i::n_workers] for i in range(n_workers)] | 把清单均匀摊到各进程 | 按步长切分而非按块,各进程负载更均衡 |
| 连接管理 | 每个子进程独立 bs.login() / bs.logout() | 多进程并发取数 | 不共享连接,避免单连接超时影响全部任务 |
| 增量起点 | 从「该股票最新日期 + 1 天」拉到今天 | 按股票精确补缺口 | 库中日期格式需为 YYYY-MM-DD,否则日期解析会失败 |
| 清洗规则 | 丢弃 close 为空、过滤 volume <= 0 | 剔除停牌与脏行 | 停牌日不会进入策略计算,形态窗口按实际交易日滚动 |
| 写入去重 | 先按日期 DELETE 再 append | 同一天重复运行 | 手工重跑当天的日常模式是安全的;但不要用它补历史 |
| 空结果 | 无新行时输出「无新数据(可能非交易日)」并返回 0 | 周末与法定节假日 | 这是预期行为,不要据此判定脚本坏了 |
怎么用 crontab 让 Sequoia-X 收盘后自动运行?
官方 README 给出的定时方式是 crontab,示例行如下。
| 片段 | 含义 | 适用场景 | 注意点 |
|---|---|---|---|
15 19 * * 1-5 | 周一到周五 19:15 执行 | A 股收盘(15:00)后数小时 | crontab 不识别节假日,节假日会照跑并输出「无新数据」 |
cd /root/Sequoia-X | 切到项目目录再执行 | 数据库相对路径为 data/sequoia_v2.db | 不切目录会按 cron 的家目录找 data/,等于换了一个空库 |
.venv/bin/python | 用虚拟环境里的解释器 | 依赖装在 venv 里 | 直接写 python 会用到系统解释器,报模块找不到 |
>> log.txt 2>&1 | 标准输出与错误都追加到日志文件 | cron 环境没有终端 | 不加这一段的输出会被丢弃,出问题无从查起 |
| 环境变量 | cron 默认不加载你的 shell 配置 | 项目用 .env + load_dotenv() 读配置 | 把飞书 Webhook 写进项目目录的 .env,不要在 cron 行里硬编码密钥 |
| 改成多时段 | 把 15 19 换成别的时间 | 数据源当日数据更新较晚时 | 当日数据未就绪时拉到空结果属正常,可顺延时间再跑一次 |
cron,做法等价:切到项目目录、用虚拟环境解释器、把输出重定向到日志文件。本项目没有内置调度器,也没有守护进程。Sequoia-X 运行耗时与影响因素
README 的自述数字只作参考,实际耗时随环境变化。
| 运行类型 | 官方自述耗时 | 主要影响因素 | 怎么判断正常 |
|---|---|---|---|
| 日常模式 | 约 2~3 分钟 | 待补股票数量、网络质量、机器 CPU 核数、策略遍历全库的耗时 | 日志里能看到「需要更新 N 只股票」与每条策略的选股数量 |
| 首次回填 | 约 12 分钟 | 单线程逐只拉取、起始日期(默认 2024-01-01)到今天的长度、重试与重连次数 | 每 500 只打印一行「已处理 x/N,成功/跳过/失败」 |
| 跳过的情形 | 数秒 | 全部股票已是最新,或当天不是交易日 | 日志出现「所有股票已是最新」或「无新数据(可能非交易日)」 |
| 策略阶段 | 未单独给出 | 逐只读库 + 向量化计算的规模;定增策略另需请求 akshare | 每个策略结束都有一行「XXXStrategy 选出 N 只股票」 |
| 推送阶段 | 逐策略最多 10 秒超时 | Webhook 响应速度;推送前还要查股票名称 | 成功会记「飞书推送成功 [key],共 N 只股票」 |
| 数据规模 | 约 5200 只 A 股(README 口径) | 股票清单来自 bs.query_stock_basic() 的上市股票过滤 | 回填开始时日志会打印「获取股票列表完成,共 N 只」 |
日志怎么读
日志用 rich 输出,格式固定为「模块名 - 消息」,带方括号时间戳。
| 日志特征 | 含义 | 适用场景 | 注意点 |
|---|---|---|---|
时间戳格式 [%Y-%m-%d %H:%M:%S] | 由 RichHandler 统一渲染 | 排查某天是否真的跑过 | 重定向到文件后不带颜色,属正常 |
| 日志级别是 DEBUG | logger 默认设为 DEBUG | 需要更细的库内输出时 | 日志会比较长,定时任务建议只保留最近若干天 |
WARNING 行带股票代码 | 单只股票计算失败被跳过,不影响其他股票 | 个别标的 K 线不足或数据异常 | 策略内异常被捕获后写 warning,主流程继续 |
| 「无选股结果,跳过推送」 | 该策略当天没有命中标的 | 阈值较严或当日行情不匹配 | 不是错误,也不会发空卡片到飞书 |
| 「主流程发生未捕获异常」+ traceback | 顶层异常处理器打印后以退出码 1 结束 | 配置缺失、数据库不可写等 | cron 不会通知你,建议配合日志检查或另设告警 |
| 同名 logger 只挂一个 handler | 重复调用取 logger 不会重复打印 | 二次开发时自定义模块取日志 | 日志不重复说明幂等逻辑生效,属正常现象 |
Sequoia-X 该什么时候跑:四个时点的实际后果
日常模式读的是「库内最新的那根 K 线」,而它写入的结束日期是 date.today()。所以「几点跑」会直接改变结果,这不是配置细节。
| 时点 | 会发生什么(源码行为) | 对你的影响 | 建议 |
|---|---|---|---|
| 开盘前(9:00 前) | 当日尚无成交,增量拉不到新行,脚本继续跑策略 | Sequoia-X 基于上一交易日数据再算一遍,可能重复推送昨天的名单 | 不要放在开盘前;确实要跑就只看日志、忽略推送 |
| 盘中(交易时段) | 结束日期是当天,baostock 返回的当日日 K 可能是尚未收盘的快照 | 若当天不再重跑,库里会留下一根不完整的当日 K 线,并被次日形态判定使用 | 不要在盘中跑;如已跑,收盘后再跑一次——源码会先 DELETE 当日记录再写入,可以覆盖 |
| 收盘后(15:30 之后) | 当日日 K 已完整,增量写入、策略按完整数据判定,命中才推送 | 这是语义上正确的时点 | crontab 建议 30 15 * * 1-5(工作日 15:30) |
| 周末与节假日 | 无新数据,日志输出「所有股票已是最新」或「无新数据」——但脚本仍会继续执行策略并推送 | 手动再跑一次会重复收到与上一个交易日完全相同的卡片 | 把定时限定在工作日;手动重跑时留意重复推送 |
| 补跑多天未运行 | 增量只会补到「最新一根」之后的日期,不会逐日重放历史 | 缺的中间交易日不会自动补齐 | 缺得多就走回填模式(已入库的会被跳过) |
main.py 与 data/engine.py 能确定的结论(已验证源码,未在本机运行验证),具体行为以官方源码为准。本站实测:非交易日跑一次会发生什么?
2026-10-10 是周六。本站用真实的 789 只库、真实的 baostock、Webhook 指向本机 mock 服务(不打扰任何群),完整跑了一次 python main.py。整个过程 49.6 秒,日志如下。
| 环节 | 日志原文 / 读数 | 说明 |
|---|---|---|
| 数据层 | 需要更新 265 只股票,启动多进程并行拉取... → sync_today_bulk: 写入 0 条数据 | 所有标的都在队列里(因为它们的最后日期 2026-10-09 早于今天),但窗口 [今天, 今天] 取不到任何数据 |
| 策略层 | MaVolumeStrategy 选出 5 只、TurtleTradeStrategy 选出 13 只、RpsBreakoutStrategy 选出 7 只(本次运行库内 265 只);高窄旗形、涨停洗盘、上升跌停、定增监控均 0 只。另一次在 789 只样本上的独立运行给出 16 / 33 / 23 只——命中数随样本规模变化,本身不说明策略优劣 | 策略没有检查「今天有没有新数据」,直接对着库里最后一根 K 线(2026-10-09)计算 |
| 推送层 | 收到 3 条真实请求:/hook/ma_volume、/hook/turtle、/hook/rps;卡片内 **日期:** 2026-10-10 | 推送照发,卡片上的日期是今天,数据其实是上一个交易日 |
| 退出码 | exit=0,日志最后一行 Sequoia-X V2 运行完成 | 从程序的角度这是一次「成功运行」,没有任何告警 |
后果一:名单会重复推送
策略读的是库里最后一根 K 线。周六、周日、以及任何一个「数据没更新的日子」,跑一次就会把同一个交易日的名单再推一遍。想在群里看「今天新增了什么」的人,会被连续几天的相同名单误导。
后果二:日期标签与数据脱节
卡片里的日期来自 date.today(),而名字来自上一交易日。周五收盘生成的名单,在周六的推送里会写「日期:周六」。事后翻记录时,很容易把这条当成周六的信号。
| 做法 | 怎么落地 | 代价 / 局限 |
|---|---|---|
| 只让交易日的 19:00 后触发 | cron 写 15 19 * * 1-5(README 的写法);Windows 任务计划只勾周一至周五 | 避开周末,但避不开节假日(周三国庆仍会触发) |
| 跑之前先判交易日 | 在 main.py 之前加一层:库内 MAX(date) 等于今天才继续;或用交易日历文件判断 | 需要自己加包装脚本;改动正式流程,建议先小范围验证 |
| 让策略只在「当天有新数据」时执行 | 把 sync_today_bulk() 的返回值 0 当作「今天没有新数据,跳过策略与推送」 | 这是最小改动:仅当返回值 > 0 才继续。但注意「非交易日返回 0」与「取数失败也返回 0」此时无法区分 |
| 推送内容带数据日期 | 卡片里把「数据日期」与「运行日期」分开写(现在只有一个日期字段) | 不改行为,只降低误读风险;属于你自己改 _build_card 的范畴 |
Windows 任务计划怎么建、日志怎么轮转?
项目 README 只给了 Linux crontab。如果你跟本站一样在 Windows 上跑,下面这套是可以直接照抄的方案(流程与命令为本站整理,未在本机注册任务,因此标注为教程扩展而非实测)。
先写一个包装脚本,解决三件事
任务计划里的命令不会帮你切目录、不会展开安全的日期文件名、也不会追加日志。先用
run_daily.bat包一层:@echo off cd /d D:\Sequoia-X for /f %%i in ('powershell -NoProfile -Command "Get-Date -Format yyyy-MM-dd"') do set TODAY=%%i if not exist logs mkdir logs .venv\Scripts\python.exe main.py >> logs\run_%TODAY%.log 2>&1 echo [%DATE% %TIME%] exit=%errorlevel% >> logs\run_%TODAY%.log要点:
cd /d是必须的(.env与data/sequoia_v2.db都按当前目录解析);%DATE%直接当文件名会带斜杠,所以用 PowerShell 取yyyy-MM-dd;日志按天一个文件,方便事后对着日期查。注册任务:每周一到周五 18:10
# 管理员 PowerShell $action = New-ScheduledTaskAction -Execute "D:\Sequoia-X\run_daily.bat" -WorkingDirectory "D:\Sequoia-X" $trigger = New-ScheduledTaskTrigger -Weekly -DaysOfWeek Monday,Tuesday,Wednesday,Thursday,Friday -At 18:10 $settings = New-ScheduledTaskSettingsSet -MultipleInstances IgnoreNew -StartWhenAvailable Register-ScheduledTask -TaskName "Sequoia-X-Daily" -Action $action -Trigger $trigger ` -Settings $settings -Description "Sequoia-X 收盘后增量+选股+飞书推送"-MultipleInstances IgnoreNew是防重入的关键:上一次还没跑完时不再启动新实例。这一条直接对应数据层的删除范围风险——同一天第二次运行会进入「只补少数标的」的分支。命令行等价写法(不支持
IgnoreNew,需要靠 XML 或 PowerShell):schtasks /Create /TN "Sequoia-X-Daily" /TR "D:\Sequoia-X\run_daily.bat" /SC WEEKLY /D MON,TUE,WED,THU,FRI /ST 18:10 /RL HIGHEST /F几点跑:三个时点各自的代价
时点 会发生什么 建议 15:05(收盘即跑) 日 K 通常还没出齐,可能取到 0 行,然后按上一交易日推名单 不要用 17:30~18:30 当日日 K 基本可得,本项目 README 给的是 19:15 可用;建议先手工跑一天确认取到当天数据 次日 08:00 数据一定齐,但当日盘中你才看到昨天的信号 只适合「不抢时点」的使用方式 开盘前 09:00 同上,且与早盘操作时间挤在一起 观察名单用 日志轮转:保留 30 天
forfiles /p D:\Sequoia-X\logs /m run_*.log /d -30 /c "cmd /c del @path"把这一行放进一个每周跑一次的任务即可。按天分文件后,删旧日志是安全的;如果你把所有输出追加到同一个
log.txt(README 的 crontab 写法),它会无限增长,而且要定位某一天得靠翻时间戳。「数据更新了但策略没跑」怎么分层定位
层 先看什么 判断 数据层 SELECT MAX(date) FROM stock_daily;是否等于今天的交易日不等于 → 取数问题(数据源限流、网络、窗口为空),策略即使跑了也是旧数据 调度层 日志文件是否存在、 exit=是多少、任务计划「上次运行结果」是否是0x0日志都没生成 → 任务没触发或工作目录不对;有日志但是空的 → 重定向写错位置 策略层 日志里是否有 7 行 执行策略:xxx只跑到「快照同步完成」就结束 → 中途抛异常(退出码 1),往上翻 Traceback 推送层 日志里 飞书推送成功 [key]还是飞书推送失败策略选出了票但推送失败 → Webhook/机器人问题,与数据无关;策略 0 只时本来就不会推送
日常运行常见问题
每天都必须手动跑一次吗?
不必。官方做法是用 crontab 在工作日收盘后自动执行,示例为 19:15。手动执行等价于把同样一条命令敲一遍,适合调试或补跑当天。具体时间与解释器路径以你的环境和官方 README 为准。
非交易日会报错吗?
不会。没有新数据时日志会输出「无新数据(可能非交易日)」并正常结束,返回写入 0 条。周末与节假日出现这条日志属于预期行为,不要据此判定脚本损坏。
同一天重复运行会写重复数据吗?
日常模式在写入前会按日期先删除当天记录再批量追加,所以手工重跑当天不会产生重复行。但注意表上还有 UNIQUE(symbol, date) 约束,回填路径遇到冲突是把 IntegrityError 静默跳过,两条路径的容错方式不同。
日常模式能补好几天没跑的数据吗?
能补上「每只股票从最新日期到今天」的缺口,但它是按日期整体删除后重写的;如果缺的是较长的历史区间,用回填模式更合适,因为它逐只从断点续拉并跳过已入库股票。选择依据见排障续传页的决策表。
cron 里跑没反应怎么办?
先看有没有重定向日志文件:命令结尾必须有 >> log.txt 2>&1,否则输出直接丢失。其次确认三件事:是否先 cd 到项目目录、是否用虚拟环境里的解释器、.env 里的必填 Webhook 是否已配置。三项都对仍无输出时,看退出码是否为 1。
跑完一次要等多久才算异常?
README 自述日常约 2~3 分钟、回填约 12 分钟,但这是项目自述值,实际取决于机器、网络与待补数量。判断是否卡住的更可靠依据是日志是否还在推进(每 500 只的进度行、每条策略的结果行)。长时间无新日志时按排障页逐项核对。
非交易日或重复运行会重复推送吗?
会。增量写入为 0 条时源码只记一行日志(例如「所有股票已是最新,无需更新」或「无新数据」),随后仍会继续执行全部策略并推送命中结果。源码里没有「当天已推送就跳过」的判断,所以周末手动重跑会重复收到与上一个交易日相同的卡片。这是读 main.py 与 data/engine.py 得出的结论,具体以官方源码为准。
盘中运行会不会把没收盘的 K 线写进库?
存在这个风险:增量查询的结束日期是 date.today(),盘中运行时当日 K 线可能还没走完。补救很简单——收盘后再跑一次,源码在写入前会先 DELETE FROM stock_daily WHERE date = ? 当日记录再追加,可以覆盖掉不完整的那一根。建议把定时固定在工作日收盘后。