Paper_Trading / TECHNICAL_DOCUMENTATION.md
superxuu
添加项目技术文档:详细记录系统架构、核心模块、问题解决方案和未来优化方向
8639143

StockReplay 项目技术文档

1. 项目概述

StockReplay 是一个基于 A 股历史行情的模拟交易复盘系统。用户可以对随机抽取的股票历史 K 线进行模拟交易,验证自己的盘感与交易策略,并在游戏结束后查看详细的交易统计与收益曲线。

1.1 核心功能

  • 盲盒选股:系统随机抽取一只符合条件的 A 股(上市满 3 年),隐藏股票名称和代码
  • 模拟交易:用户可以使用初始资金(100 万)进行买入、卖出操作
  • 行情推演:用户可以手动或自动播放 K 线,逐步揭示后续行情
  • 复盘分析:游戏结束后,展示总资产、年化收益、最大回撤、胜率等详细统计数据
  • 多市场支持:支持主板、创业板、科创板、北交所、ETF、LOF、REITs、可转债等多个市场

1.2 技术栈

  • 前端:Next.js 14 (App Router), React, TypeScript, Tailwind CSS, Zustand (状态管理), KLineCharts (图表库)
  • 后端:FastAPI (Python), DuckDB (嵌入式分析数据库), Akshare (金融数据接口)
  • 数据存储:Parquet 文件 (按月分区), Hugging Face Dataset (云端存储)
  • 部署:Hugging Face Spaces (Docker 容器)

2. 系统架构

2.1 整体架构图

┌──────────────────────────────────────────────────────────────┐
│                      Hugging Face Spaces                      │
│  ┌───────────────────────┐      ┌──────────────────────────┐ │
│  │   Frontend (Next.js)  │──────▶│   Backend (FastAPI)      │ │
│  │   - React Components  │◀──────│   - REST API Endpoints   │ │
│  │   - Zustand Store     │      │   - Core Game Logic      │ │
│  └───────────────────────┘      │   - Database Manager     │ │
│                                 └──────────────┬───────────┘ │
│                                                │              │
│                                                ▼              │
│                                 ┌──────────────────────────┐ │
│                                 │   DuckDB (In-Memory)     │ │
│                                 │   - stock_list (Table)   │ │
│                                 │   - stock_daily (View)   │ │
│                                 └──────────────┬───────────┘ │
│                                                │              │
└────────────────────────────────────────────────│──────────────┘
                                                 │
                                                 ▼
                                  ┌──────────────────────────────┐
                                  │   Hugging Face Dataset       │
                                  │   - data/stock_list.parquet  │
                                  │   - data/parquet/YYYY-MM.parquet │
                                  └──────────────────────────────┘

2.2 数据流

  1. 数据同步:本地运行 sync_data.py,从 Akshare 抓取数据,保存为 Parquet 文件,并上传至 HF Dataset
  2. 应用启动:HF Space 启动时,DatabaseManager 从 HF Dataset 下载 Parquet 文件,并在 DuckDB 中创建视图
  3. 游戏开始:前端调用 /api/game/start,后端从 stock_list 中随机筛选股票,查询 stock_daily 获取 K 线数据,返回给前端
  4. 交易过程:前端维护交易状态,后端仅提供数据查询服务

3. 核心模块详解

3.1 数据同步模块 (backend/scripts/sync_data.py)

3.1.1 功能

负责从 Akshare 接口抓取全市场(A股、ETF、LOF、REITs、可转债)的列表和日线数据,并以 Parquet 格式存储。

3.1.2 关键技术点

增量同步

  • 查询 stock_daily 视图中每个标的的最新日期
  • 如果最新日期等于最近一个交易日,则跳过
  • 否则,只抓取缺失日期的数据

多数据源适配

  • A股:ak.stock_zh_a_hist (东方财富)
  • ETF:ak.fund_etf_hist_em (东方财富)
  • LOF:ak.fund_lof_hist_em (东方财富)
  • REITs:ak.reits_hist_em (东方财富)
  • 可转债:ak.bond_zh_hs_cov_daily (新浪)

字段标准化

  • 不同接口返回的字段名不同(如 日期 vs date收盘 vs close
  • 使用 rename_map 统一转换为标准字段:trade_date, open, high, low, close, volume, amount, pct_chg, turnover_rate

并发控制

  • 使用 ThreadPoolExecutor 并发抓取
  • MAX_WORKERS 设为 5,避免触发接口频率限制
  • max_retries 设为 3,增强网络容错

3.1.3 核心代码片段

# 增量同步逻辑
existing_latest = db.conn.execute(
    "SELECT code, CAST(MAX(trade_date) AS VARCHAR) FROM stock_daily GROUP BY code"
).fetchall()
latest_map = {row[0]: row[1] for row in existing_latest}

pending = []
for t in targets:
    code = t['code']
    if code in latest_map:
        if latest_map[code] >= last_trade_day:
            continue
        start_dt = (pd.to_datetime(latest_map[code]) + timedelta(days=1)).strftime('%Y-%m-%d')
    else:
        start_dt = (datetime.now() - timedelta(days=YEARS_OF_DATA * 365)).strftime('%Y-%m-%d')
    t['start_dt'] = start_dt
    pending.append(t)

3.2 数据库管理模块 (backend/app/database.py)

3.2.1 功能

管理 DuckDB 连接,处理本地与云端两种模式下的数据加载。

3.2.2 关键技术点

双模式支持

  • 本地母本模式:如果存在 DUCKDB_PATH 环境变量且文件存在,则直接连接本地 DuckDB 文件。适用于本地开发和全量数据初始化
  • 云端无盘模式:否则,从 HF Dataset 下载 Parquet 文件,并在内存中创建 DuckDB 视图。适用于 HF Spaces 部署

视图创建与冲突解决

  • 在创建 stock_daily 视图前,必须先删除可能存在的同名表或视图,避免 Catalog Error
  • 代码:conn.execute("DROP VIEW IF EXISTS stock_daily"); conn.execute("DROP TABLE IF EXISTS stock_daily")

3.2.3 核心代码片段

# 云端模式:下载 Parquet 并创建视图
parquet_files = [f for f in all_files if f.startswith("data/parquet/") and f.endswith(".parquet")]
if parquet_files:
    local_paths = []
    for f in parquet_files:
        path = hf_hub_download(repo_id=DATASET_REPO_ID, filename=f, repo_type="dataset")
        local_paths.append(f"'{path}'")
    
    files_sql = ", ".join(local_paths)
    conn.execute("DROP VIEW IF EXISTS stock_daily")
    conn.execute("DROP TABLE IF EXISTS stock_daily")
    conn.execute(f"CREATE OR REPLACE VIEW stock_daily AS SELECT * FROM read_parquet([{files_sql}])")

3.3 游戏核心逻辑 (backend/app/core.py)

3.3.1 功能

处理游戏开始、股票筛选、K线数据获取等核心业务逻辑。

3.3.2 关键技术点

股票筛选

  • 上市满 3 年(MIN_LISTING_YEARS = 3
  • 必须存在日线数据:EXISTS (SELECT 1 FROM stock_daily sd WHERE sd.code = sl.code)

K线截断

  • 隐藏最后 100 个交易日的数据,作为"未来"行情
  • 用户从第 len(klines) - 100 根 K 线开始交易

市场分类筛选

  • 全部:所有符合条件的股票
  • 全A股:主板、创业板、科创板、北交所
  • 基金:ETF、LOF、REITs
  • ETFLOFREITs可转债:单独筛选

3.4 前端状态管理 (frontend/src/store/gameStore.ts)

3.4.1 功能

使用 Zustand 管理全局游戏状态。

3.4.2 状态结构

interface GameState {
  isPlaying: boolean;      // 游戏是否进行中
  isRevealed: boolean;     // 是否已揭晓股票名称
  isFinished: boolean;     // 回测是否结束
  realCode: string;        // 股票代码
  realName: string;        // 股票名称
  allKlines: KLine[];      // 所有K线数据
  currentIndex: number;    // 当前K线索引
  cash: number;            // 可用现金
  holdings: number;        // 持仓数量
  history: Trade[];        // 交易历史
  // ... actions
}

3.4.3 Actions

  • startGame(data):初始化游戏状态,加载 K 线数据
  • nextCandle():推进到下一根 K 线
  • buy(volume):执行买入操作
  • sell(volume):执行卖出操作
  • reveal():揭晓股票名称
  • finish():结束回测
  • reset():重置所有状态回到首页

3.5 前端组件 (frontend/src/components/)

3.5.1 TradePanel.tsx

功能:交易面板,包含买入、卖出、下一天、自动播放、揭晓、重开等功能。

关键逻辑

  • 重开按钮:点击后调用 reset(),将状态重置回首页,不自动开始新游戏
  • 自动播放:使用 setInterval 每秒调用一次 nextCandle()
  • 仓位控制:提供 1/4、1/2、3/4、全仓快捷按钮

代码示例

// 重开按钮(已优化)
<button onClick={() => { setAutoPlay(false); reset(); }}>
  <RotateCcw size={12} />
  <span>重开</span>
</button>

3.5.2 Chart.tsx

功能:K线图表,使用 klinecharts 库。

关键逻辑

  • 数据加载:监听 currentIndex 变化,更新图表显示范围
  • 指标计算:支持 MA, VOL, MACD 等指标
  • 交易标记:在图表上显示买入/卖出标记

3.5.3 StockHeader.tsx

功能:顶部股票信息栏,显示当前股票名称、代码、价格等信息。


4. 关键问题与解决方案(基于历史对话)

4.1 ETF/LOF/REITs 数据缺失

问题描述:日志显示 ETF: 1436 / 0,列表中有数据但日线数据为空

原因分析

  1. 使用了不稳定或过时的 Akshare 接口(如 fund_etf_category_sina
  2. REITs 接口返回的字段名(今开, 最新价)与预期不符,导致解析失败

解决方案

  1. 切换到稳定的东方财富接口:fund_etf_spot_em, fund_etf_hist_em, reits_realtime_em, reits_hist_em
  2. 扩展 rename_map,增加 今开 -> open, 最新价 -> close 的映射

4.2 DuckDB Table/View 冲突

问题描述Catalog Error: Existing object stock_daily is of type Table, trying to replace with type View

原因分析:本地 DuckDB 文件中 stock_daily 是一个表,但云端模式试图创建同名视图,DuckDB 不允许直接覆盖

解决方案:在创建视图前,先执行 DROP VIEW IF EXISTS stock_dailyDROP TABLE IF EXISTS stock_daily


4.3 数据同步网络超时

问题描述:大量 Read timed outConnectionResetError

原因分析:并发过高,触发数据源频率限制

解决方案

  1. 降低并发数:MAX_WORKERS 从 10 降为 5
  2. 增加重试次数:max_retries 从 2 增为 3

4.4 可转债数据解析失败

问题描述Failed to fetch sh110805 (可转债): 'date'

原因分析:部分可转债代码格式或接口返回异常,缺少 date

解决方案

  1. 增加代码格式处理:cov_symbol = code[-6:] if len(code) > 6 else code
  2. 增强字段兼容性:尝试将索引转为列 df.reset_index()
  3. 静默跳过无效数据,避免日志噪音

4.5 重开按钮逻辑优化

需求:点击"重开"或"再来一局"后,回到首页,不自动开始

解决方案:修改 TradePanel.tsx 中的按钮点击事件,移除 handleStartGame() 调用,仅调用 reset()

修改前

onClick={() => { reset(); handleStartGame(); }}

修改后

onClick={() => { setAutoPlay(false); reset(); }}

5. 部署与运维

5.1 环境变量

变量名 说明 示例
HF_TOKEN Hugging Face Token hf_xxxxx
DATASET_REPO_ID HF Dataset 仓库 ID superxu520/Paper_Trading_Data
DUCKDB_PATH 本地 DuckDB 路径(可选) /app/data/stock_data.duckdb

5.2 数据同步流程

  1. 本地配置 HF_TOKENDATASET_REPO_ID
  2. 运行 python backend/scripts/sync_data.py
  3. 脚本会自动抓取数据、保存为 Parquet、上传至 HF Dataset

5.3 Hugging Face Spaces 部署

  • 项目根目录需包含 Dockerfile
  • Dockerfile 中需设置环境变量、安装依赖、启动命令
  • HF Space 启动后,会自动从 Dataset 下载最新数据

6. 未来优化方向

  1. 数据源稳定性:寻找更稳定的可转债数据源,或增加多源切换机制
  2. 自动化同步:使用 GitHub Actions 定时触发 sync_data.py,实现每日自动更新
  3. 更多指标:支持 BOLL, KDJ, RSI 等更多技术指标
  4. 策略回测:支持用户编写策略脚本进行自动化回测
  5. 用户系统:增加用户注册、登录,保存历史战绩
  6. 社交分享:优化分享卡片,支持更多社交平台

7. 项目目录结构

Paper_Trading/
├── backend/
│   ├── app/
│   │   ├── __init__.py
│   │   ├── core.py              # 游戏核心逻辑
│   │   ├── database.py          # DuckDB 数据库管理
│   │   └── main.py              # FastAPI 主入口
│   ├── scripts/
│   │   └── sync_data.py         # 数据同步脚本
│   └── data/                    # 本地数据目录
├── frontend/
│   ├── src/
│   │   ├── app/
│   │   │   ├── page.tsx         # 主页面
│   │   │   └── layout.tsx       # 布局组件
│   │   ├── components/
│   │   │   ├── TradePanel.tsx   # 交易面板
│   │   │   ├── Chart.tsx        # K线图表
│   │   │   └── StockHeader.tsx  # 股票信息栏
│   │   ├── store/
│   │   │   └── gameStore.ts     # Zustand 状态管理
│   │   └── lib/
│   │       ├── api.ts           # API 封装
│   │       └── resample.ts      # K线重采样
│   └── public/                  # 静态资源
├── Dockerfile                   # HF Spaces 部署配置
├── README.md                    # 项目说明
└── TECHNICAL_DOCUMENTATION.md   # 本文档

8. 参考资料


文档版本:2025.02.18 最后更新:优化重开按钮逻辑、修复 DuckDB 冲突、增强 ETF/LOF/REITs 数据同步