8f016f25df
- ORM migration: models/ops 重构 - deploy: 标准化 systemd timer/service 部署体系 - MCP server: 5 个只读工具(任务/状态/数据集) - daily check: 数据一致性巡检 - watch: task_watch 后台监控 - kline_daily: 麦蕊优先,时间门禁15:00 - 删除: task_industry_sector, task_sector_features
303 lines
17 KiB
Markdown
303 lines
17 KiB
Markdown
# Agent 工作记忆
|
||
|
||
## 活跃监视
|
||
|
||
task_watch 在后台运行(PID 见 `logs/task_watch.log`),持续跟踪运行中的 task:
|
||
- 查询 `dataset_registry` 中 status='running' 的任务
|
||
- 解析 config 表 lastMessage 中的耗时数据作为历史基准
|
||
- 自适应检查间隔(剩余时间 × 0.15,clamp 15s-300s,无历史默认 60s)
|
||
|
||
## 待推进的优化(按优先级)
|
||
|
||
### P0 - 高优
|
||
|
||
- **task_watch 耗时记录入库**:当前 watch 只读历史不做记录。任务完成后应将 `dataset_registry.message` 中的 elapsed_sec 写入 config 表(或新建 `run_history` 表),供下次估算 ETA 使用。
|
||
- **task_mairui_indicators 外层超时**:与 kline_daily/kline_5min 对齐,给 `as_completed` 加上外层 timeout 兜底(`app/tasks/task_mairui_indicators.py:227`)。
|
||
|
||
### P1 - 中优
|
||
|
||
- **节假日自动更新**:`trading_calendar_holidays` 当前硬编码在 config 表。建议接入 https://github.com/shengshiyan/dataconf 或类似日历 API 自动拉取每年交易日历。
|
||
- **`daily_sync_check` 失败告警**:昨日巡检因 `FAILURE_WEBHOOK_URL/SECRET` 未配置而 exit 1(期望的失败)。需确定是否要配 webhook,或改为非致命 warning。
|
||
- **`kline_5min` mairui 偶发全 worker 挂起**:今天 recovery 4734 只失败发生在 mairui 单次 120s 全 hang。`_fetch` 已有 20s timeout + 重试,但仍抗不住 mairui 服务端全挂。建议监控 mairui 健康状态 + 失败股票重入队列。
|
||
|
||
### P2 - 低优
|
||
|
||
- **MySQL 残留配置**:`.env` 仍包含 `DB_BACKEND=mysql` 和 `MYSQL_*` 变量,config.py 已只支持 PG。可清理。
|
||
- **`stock_node` 上次 warning**:上次运行 7/10 返回 warning(41/1164 节点失败),需关注是否偶发。
|
||
- **`kline_5min` 大量 fail**:今日 recovery 4734 只失败,需单独排查 mairui 数据源。
|
||
|
||
## 设计决策记录
|
||
|
||
- **systemd timer 为主,进程内 scheduler 为辅**:宿主机直接部署用 systemd mode,Docker 部署用 docker mode。二者通过 `RUNTIME_MODE` 隔离。
|
||
- **After= 只依赖 network-online.target**:service 间不设 After= 依赖链,避免一个任务阻塞后续所有任务。
|
||
- **fail-open 策略**:节假日不过滤,让 task 在非交易日返回空数据报 warning 但不阻塞。周末由 systemd OnCalendar=Mon..Fri 保护。
|
||
|
||
## 数据集来源与依赖关系
|
||
|
||
> 数据来源与依赖关系是调度正确性的基础。原始源依赖外部 API;衍生源只读本地 PG,依赖上游原始源。
|
||
|
||
### 数据集清单
|
||
|
||
| dataset_id | 目标表 | 类型 | 来源 | 说明 |
|
||
|---|---|---|---|---|
|
||
| `stock_basic` | `market_data.stocks` | 原始源 | 麦蕊 `/hslt/list`(主)+ Baostock `query_stock_basic`(备) | 全市场 A 股代码/名称/交易所/上市状态;`stock_basic` 自身可附带雪球股本快照 |
|
||
| `kline_daily` | `market_data.kline_stock` | 原始源 | 雪球 `kline`(主)→ 麦蕊 `hsstock/history/*/d/n` → 新浪 `getKLineData`(备) | 全市场日 K OHLCV;支持 per-stock fallback |
|
||
| `kline_index` | `market_data.kline_index` + `indices` | 原始源 | 麦蕊 `hsindex/history`(主)→ 新浪指数日 K(备) | 上证/深证/创业板/沪深300/中证500/中证1000 |
|
||
| `kline_5min` | `market_data.kline_5min` | 原始源 | 麦蕊 `hsstock/history/*/5/n` | 全市场 5 分钟 K;历史深度 2023-06-14 起 |
|
||
| `tick_trade` | `market_data.tick_trade` | 原始源 | 麦蕊 `hsrl/zbjy` | 当天逐笔成交;每日 21:00 后发布 |
|
||
| `moneyflow` | `market_data.moneyflow` | 原始源 | 麦蕊 `hsstock/history/transaction` | 主力/大/中/小单净额;每日 21:30 后发布 |
|
||
| `longhubang` | `market_data.longhubang_daily` + `longhubang_seat` | 原始源 | akshare `stock_lhb_detail_em` / `stock_lhb_stock_detail_em`(东方财富封装) | 龙虎榜聚合层 + 席位层;每日 22:00 触发 |
|
||
| `stock_node` | `market_data.node_categories` + `nodes` + `stock_node_map` | 原始源 | 麦蕊 `/hszg/{list,gg}` | 股票-指数/行业/概念映射;每周六 11:30 |
|
||
| `mairui_indicators` | `market_data.kline_stock_macd_daily` + `kdj_daily` + `boll_daily` | 原始源 | 麦蕊 `hsstock/history/{macd,kdj,boll}` | 日 K MACD/KDJ/BOLL;增量拉取 |
|
||
| `share_snapshot` | `market_data.stocks` + `share` | 原始源 | 雪球 `quote_detail` | 最新总股本/流通股本;需 `XUEQIU_TOKEN` |
|
||
| `market_regime` | `market_data.market_regime_daily` | 衍生源 | 本地计算(`kline_stock`) | 涨跌家数/ advance_ratio / 恐慌标记 |
|
||
| `mairui_ma_daily` | `market_data.kline_stock_ma_daily` | 衍生源 | 本地计算(`kline_stock.close`) | MA5/10/20/60;未来可切麦蕊 `/d/maN`(需付费 licence) |
|
||
|
||
### 依赖关系
|
||
|
||
```
|
||
stock_basic ─┬─► kline_daily ─┬─► market_regime
|
||
│ └─► mairui_ma_daily
|
||
├─► kline_5min
|
||
├─► tick_trade
|
||
├─► moneyflow
|
||
├─► longhubang
|
||
├─► stock_node
|
||
└─► share_snapshot
|
||
|
||
kline_index (独立,无上游依赖)
|
||
```
|
||
|
||
- **根任务**:`stock_basic` 是大多数任务的根,提供股票代码列表。`kline_index` 独立运行。
|
||
- **关键链**:`kline_daily` 是 2 个衍生源(`market_regime`、`mairui_ma_daily`)的实际数据基础。
|
||
- **注意**:`mairui_ma_daily` 在 `app/core/sync/registry.py` 中声明依赖 `kline_daily`,但只有**调度顺序**约束;若 `kline_daily` 运行后数据又被回填(如 2026-07-15 场景),`mairui_ma_daily` 不会自动重算,需手动触发或等下次调度。
|
||
|
||
### 默认调度时间(工作日)
|
||
|
||
| 时间 | dataset_id | 备注 |
|
||
|---|---|---|
|
||
| 09:00 | `stock_basic` | 开盘前刷新股票列表 |
|
||
| 15:30 | `kline_index` | 指数日 K |
|
||
| 15:40 | `kline_daily` | 个股日 K |
|
||
| 16:00 | `kline_5min` | 5 分钟 K |
|
||
| 15:15 | `market_regime` | 市场情绪 |
|
||
| 16:30 | `mairui_ma_daily` | 日 K MA(本地计算) |
|
||
| 16:40 | `mairui_indicators` | MACD/KDJ/BOLL |
|
||
| 21:35 | `moneyflow` | 资金流(等 21:30 发布) |
|
||
| 22:00 | `longhubang` | 龙虎榜(等 19:00-21:00 出齐) |
|
||
| 周六 11:30 | `stock_node` | 节点映射 |
|
||
| 周六 11:30 | `share_snapshot` | 股本快照(雪球,周度) |
|
||
|
||
### systemd timer 与数据依赖对齐审核
|
||
|
||
总体:**基本对齐**,但存在 3 个需要注意的细节。
|
||
|
||
1. **`mairui_indicators.service` 的 `After=` 多余依赖** ✅ 已修复
|
||
- 修复前:`After=network-online.target market-sync.service market-sync-mairui-ma-daily.service`
|
||
- 修复后:`After=network-online.target market-sync.service`
|
||
- 文件:`deploy/systemd/units/market-sync-mairui-indicators.service`、`bin/systemd/market-sync-mairui-indicators.service`
|
||
- 待部署:/etc/systemd/system/ 仍保留旧版,需 `sudo bash deploy/systemd/deploy.sh`
|
||
|
||
2. **`stock_node.service` 未声明对 `stock_basic` 的依赖** ✅ 已修复
|
||
- 修复前:`After=network-online.target`
|
||
- 修复后:`After=network-online.target market-sync-morning.service`
|
||
- 文件:`deploy/systemd/units/market-sync-stock-node.service`、`bin/systemd/market-sync-stock-node.service`
|
||
- 待部署:/etc/systemd/system/ 仍保留旧版,需 `sudo bash deploy/systemd/deploy.sh`
|
||
|
||
3. **`After=` 只保证启动顺序,不保证上游成功**
|
||
- systemd 的 `After=` 不会让下游等待上游**成功完成**,仅控制 timer 触发后的排队顺序。
|
||
- 如果 `market-sync.service` 在 15:30 启动后因 `kline_daily` 卡住而 3h 超时失败,`mairui_ma_daily` 仍会在 16:30 启动(因为 timer 已触发)。
|
||
- 结果:`mairui_ma_daily` 可能基于不完整的 `kline_stock` 计算,产生类似 2026-07-15 的 3,404 行缺失。
|
||
|
||
### 数据回填级联规则
|
||
|
||
> **核心原则**:上游数据被回填或修复后,下游依赖它的衍生数据集必须手动/自动重跑,否则会出现“上游新、下游旧”的不一致。
|
||
|
||
| 上游任务/表 | 触发条件 | 必须重跑的下游任务 |
|
||
|---|---|---|
|
||
| `kline_daily` / `kline_stock` | 回填历史 K 线、修复错误日线、补充漏掉的股票/日期 | `market_regime`、`mairui_ma_daily` |
|
||
| `stock_basic` / `stocks` | 新上市/退市股票、代码变更、上市状态修正 | `kline_daily`、`kline_5min`、`tick_trade`、`moneyflow`、`longhubang`、`stock_node`、`share_snapshot` |
|
||
| `kline_stock`(任意修复) | close/volume 等核心字段修正 | `market_regime`、`mairui_ma_daily` |
|
||
|
||
**操作建议:**
|
||
|
||
- 单次少量回填(如几只股票、几天):用 CLI 参数指定 `codes` / `start` / `end`,然后按上表手动触发下游。
|
||
- 大量回填(如全市场、多月/多年历史):先跑上游,再按依赖链顺序跑下游;必要时禁用当日独立 timer,避免与 runall 并发。
|
||
- 每日巡检(`bin/daily_sync_check.py`)已增加数据一致性检查:对比 `kline_stock` 与 `kline_stock_ma_daily`、`market_regime_daily` 的最新日期与缺失行数,发现缺口即告警。
|
||
|
||
## 历史问题与解决思路
|
||
|
||
> 记录真实故障案例、排查路径和修复方法,供后续复察或参考。
|
||
|
||
### 案例 1:2026-07-15 `kline_stock_ma_daily` 缺失 3,404 行
|
||
|
||
**现象**
|
||
- `kline_stock` 11,728,408 行,`kline_stock_ma_daily` 只有 11,725,004 行,相差 3,404 行。
|
||
- 差异集中在 2026-07-14(3,400 只股票)+ SH688287 的 4 天(2026-05-29/06-04/06-05/06-08)。
|
||
- `dataset_registry` 中 `mairui_ma_daily` 状态为 `success`,`last_success_at=2026-07-14 17:11:48 UTC`。
|
||
|
||
**根因**
|
||
- `mairui_ma_daily` 在 2026-07-14 17:11 完成时,`kline_stock` 的 2026-07-14 数据还不完整(少 3,400 只)。
|
||
- `kline_daily` 在 2026-07-15 10:04 又跑了一次,补充了这 3,400 只 2026-07-14 的 K 线。
|
||
- `mairui_ma_daily` 没有自动感知上游变化,导致 MA 表缺了这 3,404 行。
|
||
|
||
**排查命令**
|
||
```bash
|
||
# 1. 对比行数
|
||
SELECT COUNT(*) FROM market_data.kline_stock;
|
||
SELECT COUNT(*) FROM market_data.kline_stock_ma_daily;
|
||
|
||
# 2. 查缺失日期分布
|
||
SELECT trade_date, COUNT(*) FROM (
|
||
SELECT stock_code, trade_date FROM market_data.kline_stock
|
||
EXCEPT
|
||
SELECT stock_code, trade_date FROM market_data.kline_stock_ma_daily
|
||
) t GROUP BY trade_date ORDER BY trade_date;
|
||
|
||
# 3. 查 dataset_registry 时间线
|
||
SELECT dataset_id, last_success_at, finished_at, message
|
||
FROM market_data.dataset_registry
|
||
WHERE dataset_id IN ('kline_daily', 'mairui_ma_daily');
|
||
```
|
||
|
||
**修复方法**
|
||
```bash
|
||
# 手动重跑 mairui_ma_daily(全量重算 ~22min)
|
||
bin/market_sync_mairui_ma_daily_run.sh
|
||
```
|
||
|
||
**预防/改进**
|
||
- 在 `bin/daily_sync_check.py` 中增加数据一致性检查:每日 23:00 对比 `kline_stock` 与 `kline_stock_ma_daily` 的覆盖差异。
|
||
- 上游回填历史数据后,按「数据回填级联规则」手动触发下游重跑。
|
||
|
||
---
|
||
|
||
### 案例 2:systemd `After=` 依赖设计缺陷
|
||
|
||
**现象**
|
||
- `market-sync-mairui-indicators.service` 写了 `After=market-sync-mairui-ma-daily.service`。
|
||
- `market-sync-stock-node.service` 没有声明对 `stock_basic` 的依赖。
|
||
|
||
**根因**
|
||
- 早期设计时对数据依赖关系梳理不够,把无依赖的任务串起来,或遗漏了必要的依赖声明。
|
||
- `systemd` 的 `After=` 只控制**启动顺序**,不检查上游任务是否**成功完成**。
|
||
|
||
**修复方法**
|
||
1. `mairui_indicators.service`:移除 `market-sync-mairui-ma-daily.service`,改为 `After=network-online.target market-sync.service`。
|
||
2. `stock_node.service`:增加 `After=market-sync-morning.service`,确保 `stock_basic` 已更新。
|
||
3. 同步修改 `deploy/systemd/units/` 和 `bin/systemd/` 下的对应文件,并执行:
|
||
```bash
|
||
sudo bash deploy/systemd/deploy.sh
|
||
```
|
||
|
||
**设计原则**
|
||
- `After=` 只声明最小必要依赖:network-online.target + 直接上游 service。
|
||
- 不要为了让下游等上游成功而过度串 service;成功/失败应通过 `daily_sync_check` 和回填规则兜底。
|
||
|
||
---
|
||
|
||
### 案例 3:`dataset_registry` 状态卡在 `running`
|
||
|
||
**现象**
|
||
- `mairui_ma_daily` 的 `sync_history` 已记录成功,但 `dataset_registry.status='running'`,`finished_at=NULL`。
|
||
- `ps` 中无对应进程。
|
||
|
||
**根因**
|
||
- 状态更新与 `sync_history` 写入不在同一事务;极端情况下任务进程异常退出,导致 `mark_sync_success` 未执行。
|
||
- 或并发/重入导致后一次 `mark_sync_running` 覆盖了前一次的成功状态。
|
||
|
||
**排查命令**
|
||
```bash
|
||
SELECT dataset_id, status, started_at, finished_at, last_success_at, message
|
||
FROM market_data.dataset_registry
|
||
WHERE dataset_id = 'mairui_ma_daily';
|
||
|
||
SELECT * FROM market_data.sync_history
|
||
WHERE dataset_id = 'mairui_ma_daily'
|
||
ORDER BY started_at DESC LIMIT 5;
|
||
```
|
||
|
||
**修复方法**
|
||
```python
|
||
from app.core.db import ops as db_ops
|
||
from datetime import datetime
|
||
|
||
db_ops.update_dataset_registry_state(
|
||
'mairui_ma_daily',
|
||
status='success',
|
||
finished_at='2026-07-15 12:02:21',
|
||
last_success_at='2026-07-15 12:02:21',
|
||
message='MA [5, 10, 20, 60] 共 11,728,408 行, 5511 只, 1349.2s',
|
||
last_error=None,
|
||
needs_resync=0,
|
||
progress_current=0,
|
||
progress_total=0,
|
||
current_step='',
|
||
updated_at=datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
|
||
)
|
||
```
|
||
|
||
**改进建议**
|
||
- 启动 `runall_once.py` 时自动调用 `recover_interrupted_dataset_registry()` 清理卡死状态(已实现)。
|
||
- `task_watch` 可升级为:发现任务 `running > 2× 历史平均耗时` 且无进程时,自动标记为 failed。
|
||
|
||
## Market Supervisor 工作流
|
||
|
||
> 本项目的 Agent 不仅是 code agent,也是 market data supervisor。修改代码/配置后必须完成部署与验证闭环,不能停留在 repo 层面。
|
||
|
||
### Agent 职责
|
||
|
||
1. **主动巡检**:定期/按需运行 `bin/daily_sync_check.py --report-only`,确认 dataset 状态、systemd drift、数据一致性。
|
||
2. **问题闭环**:发现告警后,定位根因 → 修复代码/config → 部署到 /etc → 验证告警消除。
|
||
3. **项目记忆同步**:把已确定的问题、根因、修复方法、验证命令写回 `AGENTS.md`,供后续复察。
|
||
4. **数据依赖守护**:上游数据回填/修复后,按「数据回填级联规则」触发下游重跑,并验证一致性。
|
||
|
||
### systemd unit 修改后的标准流程
|
||
|
||
任何修改了 `deploy/systemd/units/` 或 `bin/systemd/` 下 unit 文件的操作,必须:
|
||
|
||
1. **保持两边同步**:同时修改 `deploy/systemd/units/` 和 `bin/systemd/` 的对应文件(目前两者是镜像关系)。
|
||
2. **执行 deploy**:
|
||
```bash
|
||
cd /home/gao/Development/quant_home/market_sync
|
||
sudo bash deploy/systemd/deploy.sh
|
||
# 或跳过确认:sudo bash deploy/systemd/deploy.sh --yes
|
||
```
|
||
3. **验证 drift**:
|
||
```bash
|
||
.venv/bin/python bin/daily_sync_check.py --report-only
|
||
```
|
||
确保 `systemd_drift` 中 `has_drift=false`。
|
||
4. **验证 unit 状态**:
|
||
```bash
|
||
systemctl list-timers market-sync-*
|
||
systemctl status market-sync-mairui-indicators.service
|
||
systemctl status market-sync-stock-node.service
|
||
```
|
||
|
||
### 部署验证 Checklist
|
||
|
||
- [ ] repo unit 与 `/etc/systemd/system/` 内容一致(`daily_sync_check` 无 drift 告警)
|
||
- [ ] `systemctl daemon-reload` 已执行
|
||
- [ ] 相关 timer 仍 enabled(`systemctl is-enabled <name>.timer`)
|
||
- [ ] 修改后的 service 无语法错误(`systemd-analyze verify /etc/systemd/system/<name>.service`)
|
||
- [ ] 数据一致性检查通过(`kline_stock` 与下游表日期对齐)
|
||
|
||
## 已部署的优化(确认完成)
|
||
|
||
| 项目 | 日期 | 说明 |
|
||
|------|------|------|
|
||
| `RUNTIME_MODE=systemd` 写入 `.env` | 07-13 | 防止双调度器冲突 |
|
||
| `market-sync-worker.service` mask | 07-13 | systemd 模式下禁用进程内 scheduler |
|
||
| mairui_ma_daily / mairui_indicators timer 部署 | 07-13 | 2 个缺失的 timer 补部署并启用 |
|
||
| `/etc` market-sync.service 漂移修复 | 07-13 | deploy.sh 同步 repo→etc,消除 drift |
|
||
| moneyflow/lhb `After=` 依赖链修复 | 07-13 | 去掉 `After=market-sync-tick.service`,任务不再串行阻塞 |
|
||
| `bin/task_watch.py` 创建 | 07-13 | 自适应间隔的任务跟踪监视器(nohup 后台运行) |
|
||
| `runall_once.py` logger f-string 修复 | 07-13 | 超时日志改为 f-string |
|
||
| `sync/base.py` docstring 更新 | 07-13 | `_effective_sync_end` 注释过期问题修复 |
|
||
| `call_with_timeout` 超时后线程阻塞修复 | 07-13 | 改用 daemon 线程替代 `ThreadPoolExecutor`,避免超时后 `shutdown(wait=True)` 阻塞调用方 |
|
||
| `tick_trade` 外层超时加固 | 07-13 | 加上 FETCH_HARD_TIMEOUT=120s + 手动 pool 管理,防止 mairui 挂起时永久卡住 |
|
||
| `kline_daily` per-stock fallback | 07-13 | 雪球失败自动降级到 mairui/新浪,`fallback_ok` 统计备胎救回数 |
|
||
| `kline_5min` 超时后重入队列 | 07-13 | mairui 全 hang 超时后冷却 30s 重试一次失败股票,救回临时故障 |
|
||
| MCP Server SSE 模式 + systemd 服务 | 07-13 | `market-sync-mcp.service` 开机自启,SSE 端点 `http://127.0.0.1:8101/sse` |
|