05635b76b9
状态: - 9 个 sync task(stock_basic / kline_daily / kline_index / kline_5min / moneyflow / industry_sector / sector_features / share_snapshot / market_regime) - 数据源:baostock + mairui + 雪球(pysnowball) + 新浪(4 个) - 项目级约束:永远不用 akshare(已落实) - kline_5min 改用 DB 快照统一全量/增量逻辑 - 零后端 Chrome 扩展 xueqiu_sync(独立项目)
203 lines
9.4 KiB
Markdown
203 lines
9.4 KiB
Markdown
# Market Data Sync — 架构说明
|
||
|
||
> A 股市场数据定时同步框架。多个数据源 → 交易日感知调度器 → MySQL。
|
||
|
||
## 1. 目录结构
|
||
|
||
```
|
||
market_data_sync/
|
||
├── app/ # 业务代码(唯一 Python 包)
|
||
│ ├── __init__.py
|
||
│ ├── core/ # 核心抽象层(与业务无关的基础设施)
|
||
│ │ ├── config.py # 统一配置(pydantic-settings + .env)
|
||
│ │ ├── datasource/ # 数据源抽象
|
||
│ │ │ ├── base.py # DataSource / FetchResult / registry
|
||
│ │ │ └── registry.py # 健康监控 + 自动降级 + seed
|
||
│ │ ├── sync/ # 同步任务抽象
|
||
│ │ │ ├── base.py # SyncTask 基类(自动状态机)
|
||
│ │ │ └── registry.py # dataset_registry 状态机封装
|
||
│ │ ├── scheduler/ # 轻量调度器
|
||
│ │ │ └── scheduler.py # 交易日感知 + config 表持久化
|
||
│ │ ├── db/ # 数据库访问层
|
||
│ │ │ ├── connection.py # thread-local pymysql
|
||
│ │ │ ├── schema.py # 13 张表 DDL
|
||
│ │ │ └── ops.py # CRUD + 批量 upsert
|
||
│ │ └── utils/
|
||
│ │ └── logging.py # RotatingFileHandler
|
||
│ ├── sources/ # 数据源实现(5 个)
|
||
│ │ ├── sina.py # 新浪(免费 K 线)
|
||
│ │ ├── baostock.py # Baostock(基础信息+行业)
|
||
│ │ ├── mairui.py # 麦蕊智数(需 MAIRUI_LICENCE)
|
||
│ │ ├── akshare.py # akshare/东方财富(5min+资金流)
|
||
│ │ └── xueqiu.py # 雪球(股本快照)
|
||
│ ├── tasks/ # 同步任务实现(8 个)
|
||
│ │ ├── __init__.py # TASKS 注册表 + get_task()
|
||
│ │ ├── task_stocks_basic.py
|
||
│ │ ├── task_kline_daily.py
|
||
│ │ ├── task_kline_index.py
|
||
│ │ ├── task_kline_5min.py
|
||
│ │ ├── task_moneyflow.py
|
||
│ │ ├── task_industry_sector.py
|
||
│ │ ├── task_share_snapshot.py
|
||
│ │ └── task_market_regime.py
|
||
│ ├── api/ # FastAPI 管理接口(端口 8100)
|
||
│ │ ├── main.py # 入口 + 生命周期钩子
|
||
│ │ └── routes/
|
||
│ │ ├── health.py
|
||
│ │ ├── sync.py # 手动触发 / 状态查询
|
||
│ │ ├── schedule.py # 计划任务
|
||
│ │ └── datasources.py # 数据源健康度
|
||
│ └── entrypoints/ # 进程入口
|
||
│ ├── cli.py # CLI(python -m app.entrypoints.cli ...)
|
||
│ └── worker.py # 纯调度模式(python -m app.entrypoints.worker)
|
||
├── bin/ # 一次性脚本
|
||
│ └── runall_once.py # 一键串行跑全部任务
|
||
├── scripts/ # 旧 shell 脚本(保留作 fallback)
|
||
├── tests/ # 单元测试(pytest)
|
||
├── docs/ # 文档
|
||
├── data/ # 临时数据(一般为空)
|
||
├── logs/ # 运行日志(RotatingFileHandler)
|
||
├── .env / .env.example # 配置
|
||
├── requirements.txt
|
||
├── README.md
|
||
└── start.sh # 统一启动入口
|
||
```
|
||
|
||
## 2. 模块依赖
|
||
|
||
```
|
||
┌────────────────────────────────────────┐
|
||
│ entrypoints/cli │ 手动/CLI
|
||
│ entrypoints/worker │ 调度模式
|
||
│ │ │
|
||
│ ▼ │
|
||
│ ┌─────────────────────────────┐ │
|
||
│ │ api (FastAPI) │ │ HTTP 管理
|
||
│ └──────────────┬──────────────┘ │
|
||
│ │ │
|
||
▼ ▼ │
|
||
┌──────────┐ ┌─────────────┐ │
|
||
│ tasks │◄───│ core │ │
|
||
│ (8 个) │ │ ├ config │ │
|
||
└────┬─────┘ │ ├ db │ │
|
||
│ │ ├ sync │ │
|
||
│ │ ├ scheduler│ │
|
||
│ │ └ datasource│ │
|
||
▼ └──────┬──────┘ │
|
||
┌──────────┐ │ │
|
||
│ sources │◄──────────┘ │
|
||
│ (5 个) │ │
|
||
└──────────┘ │
|
||
│ │
|
||
▼ │
|
||
数据源外部 API │
|
||
```
|
||
|
||
依赖方向单向:上层 → 下层。`core` 是稳定抽象,`sources` / `tasks` 是可插拔实现。
|
||
|
||
## 3. 核心抽象
|
||
|
||
### 3.1 DataSource (`app/core/datasource/base.py`)
|
||
|
||
```python
|
||
class DataSource:
|
||
key: str # "datasource_xxx" 全局唯一
|
||
name: str # 显示名
|
||
provides: list[str] # 能力列表 ["kline_daily", "moneyflow", ...]
|
||
requires_credential: bool
|
||
|
||
def is_available(self) -> tuple[bool, str]: ...
|
||
def health_check(self) -> dict: ...
|
||
def fetch_xxx(self, ...) -> pd.DataFrame: ...
|
||
```
|
||
|
||
- `registry` 是全局单例 dict[str, DataSource]
|
||
- 启动时 `build_default_registry()` 把 5 个源注册进去
|
||
- `pick_source(capability)` 按 `provides` 找最匹配的源
|
||
|
||
### 3.2 SyncTask (`app/core/sync/base.py`)
|
||
|
||
```python
|
||
class SyncTask:
|
||
dataset_id: str # 子类必须设置
|
||
|
||
def run(self, *, trigger_source="manual", **kwargs) -> dict:
|
||
mark_sync_running(...)
|
||
try:
|
||
result = self._run(...)
|
||
mark_sync_success/failed(...)
|
||
except Exception:
|
||
mark_sync_failed(...)
|
||
```
|
||
|
||
子类只需实现 `_run()`,状态机会自动包装。
|
||
|
||
### 3.3 Scheduler (`app/core/scheduler/scheduler.py`)
|
||
|
||
- 5s tick 扫描 config 表
|
||
- 读取 `trading_calendar_holidays` 决定是否交易日
|
||
- 时间匹配(HH:MM 精确触发)或 interval(间隔秒数)触发
|
||
- 启动时 `recover_interrupted_syncs()` 把卡死的 `running` 任务改 `failed`
|
||
|
||
## 4. 数据流(以 kline_daily 为例)
|
||
|
||
```
|
||
scheduler tick
|
||
└─ 时间到 (16:00)
|
||
└─ ScheduleJob.run()
|
||
└─ tasks.get_task("kline_daily")()
|
||
└─ SyncKlineDaily.run()
|
||
├─ mark_sync_running(...) ← 状态机
|
||
├─ _ensure_health_checked() ← 拉一次数据源健康
|
||
├─ pick_source("kline_daily") ← 选雪球(主) > 新浪 > baostock
|
||
├─ 多线程 fetch (10 workers) ← XUEQIU_TOKEN 限速
|
||
├─ 批量 upsert (CHUNK=500) ← pymysql executemany
|
||
└─ mark_sync_success(...) ← 状态机
|
||
```
|
||
|
||
## 5. 添加新数据源
|
||
|
||
1. 在 `app/sources/` 新建 `my_source.py`
|
||
2. 继承 `DataSource`,设置 `key` / `name` / `provides` / `requires_credential`
|
||
3. 实现 `is_available()` / `health_check()` / 业务方法
|
||
4. 在 `app/core/datasource/registry.py` 的 `build_default_registry()` 注册
|
||
|
||
## 6. 添加新同步任务
|
||
|
||
1. 在 `app/tasks/` 新建 `task_xxx.py`
|
||
2. 继承 `SyncTask`,设置 `dataset_id`
|
||
3. 实现 `_run()`,返回 `{"status": "ok", "message": "..."}` 或 `{"status": "warning/blocked", ...}`
|
||
4. 在 `app/tasks/__init__.py` 的 `TASKS` 字典中加一行
|
||
5. (可选)在 `app/core/sync/registry.py` 的 `SYNC_DEFINITIONS` 加条目
|
||
|
||
## 7. 数据库表
|
||
|
||
13 张表在 `app/core/db/schema.py`:
|
||
|
||
| 表 | 用途 |
|
||
|---|---|
|
||
| `stocks` | 股票基础信息 |
|
||
| `kline_stock` | 日 K 线 |
|
||
| `kline_index` | 指数日 K |
|
||
| `kline_5min` | 5min K 线 |
|
||
| `moneyflow` | 资金流 |
|
||
| `industry` / `sectors` / `stock_sector_map` | 行业映射 |
|
||
| `share` | 股本快照 |
|
||
| `market_regime_daily` | 市场情绪 |
|
||
| `dataset_registry` | 同步任务状态机 |
|
||
| `config` | 通用配置(调度、节假日、token 等) |
|
||
| `indices` | 指数字典 |
|
||
|
||
## 8. 配置(.env)
|
||
|
||
| 变量 | 必填 | 说明 |
|
||
|---|---|---|
|
||
| `MYSQL_HOST/PORT/USER/PASSWORD/DATABASE` | ✓ | 主库 |
|
||
| `MYSQL_DATABASE_BUSINESS` | | 业务库(默认 grid_seeker_model_base) |
|
||
| `XUEQIU_TOKEN` | | 雪球 token(K线主源 + 股本) |
|
||
| `MAIRUI_LICENCE` | | 麦蕊智数 licence |
|
||
| `HOLIDAYS_LIST` | | A 股休市日(逗号分隔 YYYY-MM-DD) |
|
||
| `SCHEDULER_AUTO_SEED` | | 启动时 seed 默认计划任务 |
|
||
| `LOG_DIR` | | 日志目录(默认 logs) |
|
||
| `LOG_LEVEL` | | 日志级别(默认 INFO) |
|