ashare-quant Web 应用设计方案(v4 · 双进程 + 桌面控制台 + 市场注册表)
本文档描述当前已实现的系统全貌。v4 最大的变化是从”单进程 Web 服务”升级为 双进程架构:一个轻量 tkinter 桌面控制台(GUI) + 一个独立后台服务进程**。 Web 应用(Flask 3.1.1 + Jinja2 + Bootstrap5 + ECharts,端口 5188)由后台服务进程承载, 由 GUI 按需启停、改端口、看全局日志、管理数据。
变更记录:v1 三页草案 → v2 五页 + 自定义因子 + LLM → v3 反映全部实现 → v4 双进程 + GUI 单入口 + 日志中控 + 市场注册表(配置驱动,TDX 优先)。
1. 总体架构(v4:双进程)
┌──────────────────────────────────────────────────────────────────┐
│ GUI 进程 gui.py (tkinter,仅标准库,瞬启) │
│ ┌────────────┬─────────────┬──────────────┬─────────────┐ │
│ │ 控制台 │ 服务 │ 数据/市场 │ 任务 │ │
│ │ (全局日志) │ (Web 启停) │ (注册表/导入) │ (Job 状态) │ │
│ └────────────┴─────────────┴──────────────┴─────────────┘ │
│ · 启动后台服务进程(自动拉起) │
│ · 增量拉取日志 /ctrl/logs?after= · 启停 Web /ctrl/flask/* │
│ · 看市场 /ctrl/markets · 提交任务 /ctrl/jobs/submit │
└───────────────────────────────┬──────────────────────────────────┘
│ 控制通道 HTTP 127.0.0.1:5190(stdlib)
┌───────────────────────────────▼──────────────────────────────────┐
│ 服务进程 core/service.py (双进程之"服务") │
│ core/ │
│ ├─ loghub.py LogHub 全局日志中控(独立线程+环形缓冲+Tee) │
│ ├─ apppool.py AppPool 统一线程池 + Flask 服务线程启停 │
│ ├─ control.py ControlServer 控制通道路由 │
│ └─ service.py ServiceFacade 装配 + 市场概览按需刷新 │
│ webapp/ Flask 3 (JobManager 注入 AppPool.executor 统一线程池) │
│ data/ fetchers · store(daily 带 market 列 + symbols 主表) │
│ markets.py(注册表) · pipeline · calendar │
│ factors/ strategy/ backtest/ analysis/ execution/ │
└───────────────────────────────────────────────────────────────────┘
关键设计(v4 新增):
- 单入口:
python gui.py。GUI 启动后台服务进程(CREATE_NEW_PROCESS_GROUP), 轮询控制通道;关窗时经/ctrl/shutdown优雅退出并 terminate 子进程。 - 单例模式(v4.1):同名互斥量(
CreateMutexW)+ 命名唤醒事件保证同时只运行 一个 GUI 实例;重复执行python gui.py时,向已运行实例置位唤醒事件、由等待线程 把其窗口带到前台(deiconify+lift+短暂 topmost+focus_force),新进程退出。 - 现代深色主题(v4.1):
clam基底重排 layout(扁平 Notebook 页签 / Treeview 头部), 靛蓝强调色 + Segoe UI;品牌顶栏(连接状态呼吸点)、扁平主/次/危险按钮、 深色 Treeview(状态着色、交替行)、按级别着色控制台日志。 - 日志全中控(需求:logging 线程中央控制):服务进程把
sys.stdout/sys.stderr换成_Tee(同时写真实终端 + 入队),再加loggingHandler——所有print / logging / werkzeug/Flask输出统一进 LogHub 独立线程 → 线程安全环形缓冲(LOG_BUF_MAX)。 GUI“控制台”页签按seq增量拉取,不丢行、不重复。 - 双进程职责:GUI 只负责界面与展示(轻量、瞬启、不依赖 pandas/flask);重活 (数据/回测/信号/Web 服务)全在服务进程;双方经 5190 控制通道通信。
- 统一线程池:
AppPool持有一个ThreadPoolExecutor,既承载数据/回测长任务 (注入 JobManager),又运行 Flask 服务线程(werkzeug make_server,可优雅启停)。 - 市场注册表(配置驱动,可插拔扩展市场):见 §7。
daily表加market列, 新增symbols(market,symbol,name,kind,extra)主表;历史库已迁移为各市场分列统计。 - TDX 优先:
TDX_DIR=C:\Tools\TDX为默认导入源(vipdoc/{sh,sz}/lday32B 记录、 指数/板块 164B 记录),市场注册表里每市场带tdx/adapter/online数据源配置。
保留的设计(v3,依旧成立):核心模块不改,main.py/run_web.py 与 Web 共用
data/pipeline.py、backtest/pipeline.py(带 progress(pct,msg) 回调);长任务一律走
JobManager 线程池;静态资源 ?v= 版本号 + no-cache;因子面板/模拟盘 60s TTL 缓存;
前端 10s 心跳 /api/health。
# 启动方式(三选一,推荐第一条)
python gui.py # 桌面控制台(单入口,自动拉起服务进程)
python run_web.py # 兼容 shim:直接启动服务 + 自动启 Web → 5188
python main.py ... # 兼容 CLI(update/today/backtest/...)
2. 五页面(Web,已实现功能)
2.1 仪表盘 /dashboard
- 顶部全局卡:数据状态(股票/日线/分红/基准)、策略库(已存策略数 + 名称)
- 其下策略选择框(无”全部”选项;默认选中:上次选择 > 模拟盘策略 > 有回测数据的策略)
- 按策略显示:最近回测卡、模拟盘卡、两条净值 mini 曲线
2.2 数据管理 /data
- 状态卡:股票数 / 日线记录 / 分红事件 / 大小与最近更新(按市场分列)
- 下载设置:起止日期、复权、范围、分红、基准 → “增量更新(向后追加)”
- ⚡ 快速更新今日行情(约30秒):腾讯批量行情接口(qt.gtimg.cn,50 只/次)
- 数据维护:补全历史 / 补全股票列表(新股) / 导入本地日线(通达信/同花顺)
- 任务进度条 + 实时日志;股票列表(搜索+翻页)/ 数据覆盖 两 Tab;状态持久化
2.3 策略生成 /strategy
三列布局:择股 / 调仓 / 择时 / 自定义因子;中列因子与打分 + 策略保存/加载;右列预览; 底部 LLM 对话面板(OpenAI 兼容,默认 DeepSeek,SSE 流式,会话持久化)。 生成调仓信号 → 模拟盘(后台任务):完整历史信号序列,覆盖式写入。
2.4 策略回测 /backtest
左:策略/日期/资金/复权/基准/成本;主区:6 指标卡、净值对比、回撤/逐年/交易/信号 Tab;
结果缓存 outputs/results/<job_id>.json;“附回测结果→LLM”。
2.5 模拟盘 /paper
策略 + 起始 + 结束 + “开始模拟”;状态卡、净值、持仓/已平仓、调仓记录; 覆盖式记录、时间窗口、合成初始买入、全程空仓保持现金。
3. 策略配置模型(StrategyConfig,与 v3 一致)
pool(择股)· strategy(top_n/div_rank_n/min_div_years/rebalance_freq 等)·
factors(form/expression/代码三模式)。执行语义:调仓日生成、cap_only/target
减仓风格、择时空仓目标 {} 为合法信号。
4. 模拟盘数据模型(与 v3 一致)
outputs/paper_account.json:[{signal_date, strategy, generated_at, target}];
按 strategy 覆盖;回放”次日开盘成交”与回测同口径。
5. 页面状态持久化(localStorage,与 v3 一致)
所有页面表单变化即保存、切换自动恢复(common.js loadPageState/savePageState)。
6. REST API 清单(当前实际,46 条,与 v3 一致)
页面路由 + /api/data、/api/strategies、/api/factors、/api/backtest、
/api/jobs、/api/paper、/api/dashboard、/api/llm、/api/health。
(数据页 /api/data/status 现返回按市场的 markets 概览。)
7. 市场注册表(v4 新增)
config/markets.json —— 配置驱动的市场注册表:
{
"markets": [
{"id": "A", "name": "A股-个股", "kind": "stock", "enabled": true,
"tdx": {"dir": "vipdoc", "subdirs": ["sh", "sz", "bj"]},
"adapter": "ashare", "online": ["akshare"], "note": "个股(含退市股)"},
{"id": "A_index", "name": "A股-指数", "kind": "index", "enabled": true,
"tdx": {"dir": "vipdoc", "subdirs": ["sh", "sz"]}, "adapter": "ashare_index_tdx",
"online": ["akshare"], "note": "沪深指数,tdx 指数 32B/164B 记录"},
{"id": "A_sector", "name": "A股-板块", "kind": "sector", "enabled": true,
"tdx": {"dir": "vipdoc", "subdirs": ["sh", "sz"]}, "adapter": "ashare_sector_tdx",
"online": [], "note": "本策略/概念板块(880/885 前缀)"},
{"id": "HK", "name": "港股", "kind": "stock", "enabled": false,
"adapter": "hk", "online": ["akshare"], "note": "扩展市场(预留)"},
{"id": "US", "name": "美股", "kind": "stock", "enabled": false,
"adapter": "us", "online": ["akshare"], "note": "扩展市场(预留)"},
{"id": "COMMODITY", "name": "大宗商品", "kind": "quotes", "enabled": false,
"adapter": "commodity", "online": [], "note": "扩展市场(预留)"},
{"id": "CRYPTO", "name": "虚拟币", "kind": "quotes", "enabled": false,
"adapter": "crypto", "online": ["binance"], "note": "扩展市场(预留)"}
]
}
enabled:true才会被导入全部启用市场(data/markets.import_all_enabled)处理; 单一市场用import_tdx(market, tdx_dir, progress)。market_id='A'特判 → 交给data/pipeline.import_tdx_data(保留既有 A 股个股导入逻辑)。- 数据落库带
market列:daily主键(symbol,date,adjust,market);symbols主表记录(market, symbol, name, kind, extra)。 - 扩展市场(港股/美股/大宗/虚拟币)均可插拔:在注册表加一行 + 提供
adapter即可,无需改动核心导入流程;当前默认enabled:false。 - GUI“数据/市场”页签按注册表展示,支持启用/停用(写回 markets.json)、单市场/全量导入。
8. 代码结构(当前实际)
ashare-quant/
├── config/
│ ├── settings.py # 策略参数、端口(5188/5190)、TDX_DIR、LOG_BUF_MAX、MARKETS_CONFIG
│ └── markets.json # 市场注册表(A/A_index/A_sector/HK/US/COMMODITY/CRYPTO)
├── core/ # 双进程核心(服务进程)
│ ├── loghub.py # LogHub:独立 logging 线程 + 环形缓冲 + Tee(stdout/stderr) + logging Handler
│ ├── apppool.py # AppPool:统一 ThreadPoolExecutor + Flask(make_server) 线程启停
│ ├── control.py # ControlServer:GUI↔服务控制通道(/ctrl/status|logs|flask|jobs|markets|db|shutdown)
│ └── service.py # ServiceFacade:装配 + 任务派发(_JOB_DISPATCH)+ 市场概览按需刷新
├── data/
│ ├── markets.py # 注册表加载 + import_tdx/import_all_enabled(market='A' 特判 → pipeline)
│ ├── pipeline.py # update_data / update_today / backfill / refresh_list / import_tdx_data
│ ├── fetchers/akshare_fetcher.py
│ ├── store/sqlite_store.py# daily(market 列) + symbols 主表 + 迁移 + 市场感知查询
│ └── calendar.py
├── webapp/
│ ├── __init__.py jobs.py llm.py paper.py
│ ├── api/ pages.py data_api.py strategies_api.py backtest_api.py
│ │ paper_api.py dashboard_api.py llm_api.py
│ └── templates/ static/ # Bootstrap5/ECharts/marked
├── factors/ strategy/ backtest/ analysis/ execution/ strategies/ sessions/
├── gui.py # 【推荐入口】tkinter 桌面控制台(单入口=双进程)
├── run_web.py # 兼容 shim:启动服务 + 自动启 Web
├── main.py # 兼容 CLI(已接入 LogHub)
├── tests/ outputs/
└── config/llm.json # LLM 配置(gitignore)
9. 控制通道 API(GUI ↔ 服务进程,v4 新增)
core/control.py,ThreadingHTTPServer,默认 127.0.0.1:5190:
| 方法 & 路径 | 说明 |
|---|---|
GET /ctrl/status | 服务状态(flask/job_running/uptime) |
GET /ctrl/logs?after=&level= | 增量拉取全局日志(seq 游标) |
GET /ctrl/flask/status · POST /ctrl/flask/start{port} · POST /ctrl/flask/stop | Web 服务启停/改端口 |
GET /ctrl/jobs?kind= · POST /ctrl/jobs/submit{kind,params} | 任务列表/提交(update/today/backfill/refresh_list/markets_import/backtest/signal) |
GET /ctrl/markets · POST /ctrl/markets/import{market,dir} · POST /ctrl/markets/toggle{market,enabled} | 市场注册表/导入/启停 |
GET /ctrl/db/status | 数据库大小 + 分市场概览 |
POST /ctrl/shutdown | 优雅关闭服务进程 |
按需刷新:市场概览(各市场标的/K线数)用
market_summary缓存 + 短 TTL + 进行中保护(_maybe_refresh_summary),GUI 打开”数据/市场”页签时才扫描、导入/更新 完成后立即_bump_summary_refresh——避免对 3.5GB 库做固定周期全表扫描。
10. 已知限制与注意事项
- 本地单用户工具:仅绑定 127.0.0.1;api_key 明文存本机(gitignore)
- 控制通道 5190 / Web 5188 端口:GUI 启动服务进程监听 5190;Web 由 GUI 在 5188
启动(可改端口)。关窗会
/ctrl/shutdown优雅退出并 terminate 子进程。 - 每日调仓/全市场信号生成较慢:后台任务,数据量大后更久
- 市场概览缓存延迟:导入/更新后概览数字会稍后(按需刷新)更新,非实时
- 残留服务器进程:若 GUI 异常退出未关闭,可
taskkill /F /IM python.exe或重启 (正常关窗无此问题) - 模板改动需检查 div 配平/嵌套;改前端 JS 后 Ctrl+F5(已加 ?v= + no-cache)
- 基准对比:000922/000300 为价格指数,红利策略最优基准是 H00922 全收益
评论