Spaces:
Running
Running
superxuu commited on
Commit ·
8639143
1
Parent(s): d25ae45
添加项目技术文档:详细记录系统架构、核心模块、问题解决方案和未来优化方向
Browse files- TECHNICAL_DOCUMENTATION.md +393 -0
TECHNICAL_DOCUMENTATION.md
ADDED
|
@@ -0,0 +1,393 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# StockReplay 项目技术文档
|
| 2 |
+
|
| 3 |
+
## 1. 项目概述
|
| 4 |
+
|
| 5 |
+
**StockReplay** 是一个基于 A 股历史行情的模拟交易复盘系统。用户可以对随机抽取的股票历史 K 线进行模拟交易,验证自己的盘感与交易策略,并在游戏结束后查看详细的交易统计与收益曲线。
|
| 6 |
+
|
| 7 |
+
### 1.1 核心功能
|
| 8 |
+
- **盲盒选股**:系统随机抽取一只符合条件的 A 股(上市满 3 年),隐藏股票名称和代码
|
| 9 |
+
- **模拟交易**:用户可以使用初始资金(100 万)进行买入、卖出操作
|
| 10 |
+
- **行情推演**:用户可以手动或自动播放 K 线,逐步揭示后续行情
|
| 11 |
+
- **复盘分析**:游戏结束后,展示总资产、年化收益、最大回撤、胜率等详细统计数据
|
| 12 |
+
- **多市场支持**:支持主板、创业板、科创板、北交所、ETF、LOF、REITs、可转债等多个市场
|
| 13 |
+
|
| 14 |
+
### 1.2 技术栈
|
| 15 |
+
- **前端**:Next.js 14 (App Router), React, TypeScript, Tailwind CSS, Zustand (状态管理), KLineCharts (图表库)
|
| 16 |
+
- **后端**:FastAPI (Python), DuckDB (嵌入式分析数据库), Akshare (金融数据接口)
|
| 17 |
+
- **数据存储**:Parquet 文件 (按月分区), Hugging Face Dataset (云端存储)
|
| 18 |
+
- **部署**:Hugging Face Spaces (Docker 容器)
|
| 19 |
+
|
| 20 |
+
---
|
| 21 |
+
|
| 22 |
+
## 2. 系统架构
|
| 23 |
+
|
| 24 |
+
### 2.1 整体架构图
|
| 25 |
+
|
| 26 |
+
```
|
| 27 |
+
┌──────────────────────────────────────────────────────────────┐
|
| 28 |
+
│ Hugging Face Spaces │
|
| 29 |
+
│ ┌───────────────────────┐ ┌──────────────────────────┐ │
|
| 30 |
+
│ │ Frontend (Next.js) │──────▶│ Backend (FastAPI) │ │
|
| 31 |
+
│ │ - React Components │◀──────│ - REST API Endpoints │ │
|
| 32 |
+
│ │ - Zustand Store │ │ - Core Game Logic │ │
|
| 33 |
+
│ └───────────────────────┘ │ - Database Manager │ │
|
| 34 |
+
│ └──────────────┬───────────┘ │
|
| 35 |
+
│ │ │
|
| 36 |
+
│ ▼ │
|
| 37 |
+
│ ┌──────────────────────────┐ │
|
| 38 |
+
│ │ DuckDB (In-Memory) │ │
|
| 39 |
+
│ │ - stock_list (Table) │ │
|
| 40 |
+
│ │ - stock_daily (View) │ │
|
| 41 |
+
│ └──────────────┬───────────┘ │
|
| 42 |
+
│ │ │
|
| 43 |
+
└────────────────────────────────────────────────│──────────────┘
|
| 44 |
+
│
|
| 45 |
+
▼
|
| 46 |
+
┌──────────────────────────────┐
|
| 47 |
+
│ Hugging Face Dataset │
|
| 48 |
+
│ - data/stock_list.parquet │
|
| 49 |
+
│ - data/parquet/YYYY-MM.parquet │
|
| 50 |
+
└──────────────────────────────┘
|
| 51 |
+
```
|
| 52 |
+
|
| 53 |
+
### 2.2 数据流
|
| 54 |
+
1. **数据同步**:本地运行 `sync_data.py`,从 Akshare 抓取数据,保存为 Parquet 文件,并上传至 HF Dataset
|
| 55 |
+
2. **应用启动**:HF Space 启动时,`DatabaseManager` 从 HF Dataset 下载 Parquet 文件,并在 DuckDB 中创建视图
|
| 56 |
+
3. **游戏开始**:前端调用 `/api/game/start`,后端从 `stock_list` 中随机筛选股票,查询 `stock_daily` 获取 K 线数据,返回给前端
|
| 57 |
+
4. **交易过程**:前端维护交易状态,后端仅提供数据查询服务
|
| 58 |
+
|
| 59 |
+
---
|
| 60 |
+
|
| 61 |
+
## 3. 核心模块详解
|
| 62 |
+
|
| 63 |
+
### 3.1 数据同步模块 (`backend/scripts/sync_data.py`)
|
| 64 |
+
|
| 65 |
+
#### 3.1.1 功能
|
| 66 |
+
负责从 Akshare 接口抓取全市场(A股、ETF、LOF、REITs、可转债)的列表和日线数据,并以 Parquet 格式存储。
|
| 67 |
+
|
| 68 |
+
#### 3.1.2 关键技术点
|
| 69 |
+
|
| 70 |
+
**增量同步**:
|
| 71 |
+
- 查询 `stock_daily` 视图中每个标的的最新日期
|
| 72 |
+
- 如果最新日期等于最近一个交易日,则跳过
|
| 73 |
+
- 否则,只抓取缺失日期的数据
|
| 74 |
+
|
| 75 |
+
**多数据源适配**:
|
| 76 |
+
- A股:`ak.stock_zh_a_hist` (东方财富)
|
| 77 |
+
- ETF:`ak.fund_etf_hist_em` (东方财富)
|
| 78 |
+
- LOF:`ak.fund_lof_hist_em` (东方财富)
|
| 79 |
+
- REITs:`ak.reits_hist_em` (东方财富)
|
| 80 |
+
- 可转债:`ak.bond_zh_hs_cov_daily` (新浪)
|
| 81 |
+
|
| 82 |
+
**字段标准化**:
|
| 83 |
+
- 不同接口返回的字段名不同(如 `日期` vs `date`,`收盘` vs `close`)
|
| 84 |
+
- 使用 `rename_map` 统一转换为标准字段:`trade_date`, `open`, `high`, `low`, `close`, `volume`, `amount`, `pct_chg`, `turnover_rate`
|
| 85 |
+
|
| 86 |
+
**并发控制**:
|
| 87 |
+
- 使用 `ThreadPoolExecutor` 并发抓取
|
| 88 |
+
- `MAX_WORKERS` 设为 5,避免触发接口频率限制
|
| 89 |
+
- `max_retries` 设为 3,增强网络容错
|
| 90 |
+
|
| 91 |
+
#### 3.1.3 核心代码片段
|
| 92 |
+
|
| 93 |
+
```python
|
| 94 |
+
# 增量同步逻辑
|
| 95 |
+
existing_latest = db.conn.execute(
|
| 96 |
+
"SELECT code, CAST(MAX(trade_date) AS VARCHAR) FROM stock_daily GROUP BY code"
|
| 97 |
+
).fetchall()
|
| 98 |
+
latest_map = {row[0]: row[1] for row in existing_latest}
|
| 99 |
+
|
| 100 |
+
pending = []
|
| 101 |
+
for t in targets:
|
| 102 |
+
code = t['code']
|
| 103 |
+
if code in latest_map:
|
| 104 |
+
if latest_map[code] >= last_trade_day:
|
| 105 |
+
continue
|
| 106 |
+
start_dt = (pd.to_datetime(latest_map[code]) + timedelta(days=1)).strftime('%Y-%m-%d')
|
| 107 |
+
else:
|
| 108 |
+
start_dt = (datetime.now() - timedelta(days=YEARS_OF_DATA * 365)).strftime('%Y-%m-%d')
|
| 109 |
+
t['start_dt'] = start_dt
|
| 110 |
+
pending.append(t)
|
| 111 |
+
```
|
| 112 |
+
|
| 113 |
+
---
|
| 114 |
+
|
| 115 |
+
### 3.2 数据库管理模块 (`backend/app/database.py`)
|
| 116 |
+
|
| 117 |
+
#### 3.2.1 功能
|
| 118 |
+
管理 DuckDB 连接,处理本地与云端两种模式下的数据加载。
|
| 119 |
+
|
| 120 |
+
#### 3.2.2 关键技术点
|
| 121 |
+
|
| 122 |
+
**双模式支持**:
|
| 123 |
+
- **本地母本模式**:如果存在 `DUCKDB_PATH` 环境变量且文件存在,则直接连接本地 DuckDB 文件。适用于本地开发和全量数据初始化
|
| 124 |
+
- **云端无盘模式**:否则,从 HF Dataset 下载 Parquet 文件,并在内存中创建 DuckDB 视图。适用于 HF Spaces 部署
|
| 125 |
+
|
| 126 |
+
**视图创建与冲突解决**:
|
| 127 |
+
- 在创建 `stock_daily` 视图前,必须先删除可能存在的同名表或视图,避免 `Catalog Error`
|
| 128 |
+
- 代码:`conn.execute("DROP VIEW IF EXISTS stock_daily"); conn.execute("DROP TABLE IF EXISTS stock_daily")`
|
| 129 |
+
|
| 130 |
+
#### 3.2.3 核心代码片段
|
| 131 |
+
|
| 132 |
+
```python
|
| 133 |
+
# 云端模式:下载 Parquet 并创建视图
|
| 134 |
+
parquet_files = [f for f in all_files if f.startswith("data/parquet/") and f.endswith(".parquet")]
|
| 135 |
+
if parquet_files:
|
| 136 |
+
local_paths = []
|
| 137 |
+
for f in parquet_files:
|
| 138 |
+
path = hf_hub_download(repo_id=DATASET_REPO_ID, filename=f, repo_type="dataset")
|
| 139 |
+
local_paths.append(f"'{path}'")
|
| 140 |
+
|
| 141 |
+
files_sql = ", ".join(local_paths)
|
| 142 |
+
conn.execute("DROP VIEW IF EXISTS stock_daily")
|
| 143 |
+
conn.execute("DROP TABLE IF EXISTS stock_daily")
|
| 144 |
+
conn.execute(f"CREATE OR REPLACE VIEW stock_daily AS SELECT * FROM read_parquet([{files_sql}])")
|
| 145 |
+
```
|
| 146 |
+
|
| 147 |
+
---
|
| 148 |
+
|
| 149 |
+
### 3.3 游戏核心逻辑 (`backend/app/core.py`)
|
| 150 |
+
|
| 151 |
+
#### 3.3.1 功能
|
| 152 |
+
处理游戏开始、股票筛选、K线数据获取等核心业务逻辑。
|
| 153 |
+
|
| 154 |
+
#### 3.3.2 关键技术点
|
| 155 |
+
|
| 156 |
+
**股票筛选**:
|
| 157 |
+
- 上市满 3 年(`MIN_LISTING_YEARS = 3`)
|
| 158 |
+
- 必须存在日线数据:`EXISTS (SELECT 1 FROM stock_daily sd WHERE sd.code = sl.code)`
|
| 159 |
+
|
| 160 |
+
**K线截断**:
|
| 161 |
+
- 隐藏最后 100 个交易日的数据,作为"未来"行情
|
| 162 |
+
- 用户从第 `len(klines) - 100` 根 K 线开始交易
|
| 163 |
+
|
| 164 |
+
**市场分类筛选**:
|
| 165 |
+
- `全部`:所有符合条件的股票
|
| 166 |
+
- `全A股`:主板、创业板、科创板、北交所
|
| 167 |
+
- `基金`:ETF、LOF、REITs
|
| 168 |
+
- `ETF`、`LOF`、`REITs`、`可转债`:单独筛选
|
| 169 |
+
|
| 170 |
+
---
|
| 171 |
+
|
| 172 |
+
### 3.4 前端状态管理 (`frontend/src/store/gameStore.ts`)
|
| 173 |
+
|
| 174 |
+
#### 3.4.1 功能
|
| 175 |
+
使用 Zustand 管理全局游戏状态。
|
| 176 |
+
|
| 177 |
+
#### 3.4.2 状态结构
|
| 178 |
+
|
| 179 |
+
```typescript
|
| 180 |
+
interface GameState {
|
| 181 |
+
isPlaying: boolean; // 游戏是否进行中
|
| 182 |
+
isRevealed: boolean; // 是否已揭晓股票名称
|
| 183 |
+
isFinished: boolean; // 回测是否结束
|
| 184 |
+
realCode: string; // 股票代码
|
| 185 |
+
realName: string; // 股票名称
|
| 186 |
+
allKlines: KLine[]; // 所有K线数据
|
| 187 |
+
currentIndex: number; // 当前K线索引
|
| 188 |
+
cash: number; // 可用现金
|
| 189 |
+
holdings: number; // 持仓数量
|
| 190 |
+
history: Trade[]; // 交易历史
|
| 191 |
+
// ... actions
|
| 192 |
+
}
|
| 193 |
+
```
|
| 194 |
+
|
| 195 |
+
#### 3.4.3 Actions
|
| 196 |
+
|
| 197 |
+
- `startGame(data)`:初始化游戏状态,加载 K 线数据
|
| 198 |
+
- `nextCandle()`:推进到下一根 K 线
|
| 199 |
+
- `buy(volume)`:执行买入操作
|
| 200 |
+
- `sell(volume)`:执行卖出操作
|
| 201 |
+
- `reveal()`:揭晓股票名称
|
| 202 |
+
- `finish()`:结束回测
|
| 203 |
+
- `reset()`:重置所有状态回到首页
|
| 204 |
+
|
| 205 |
+
---
|
| 206 |
+
|
| 207 |
+
### 3.5 前端组件 (`frontend/src/components/`)
|
| 208 |
+
|
| 209 |
+
#### 3.5.1 `TradePanel.tsx`
|
| 210 |
+
|
| 211 |
+
**功能**:交易面板,包含买入、卖出、下一天、自动播放、揭晓、重开等功能。
|
| 212 |
+
|
| 213 |
+
**关键逻辑**:
|
| 214 |
+
- **重开按钮**:点击后调用 `reset()`,将状态重置回首页,不自动开始新游戏
|
| 215 |
+
- **自动播放**:使用 `setInterval` 每秒调用一次 `nextCandle()`
|
| 216 |
+
- **仓位控制**:提供 1/4、1/2、3/4、全仓快捷按钮
|
| 217 |
+
|
| 218 |
+
**代码示例**:
|
| 219 |
+
```typescript
|
| 220 |
+
// 重开按钮(已优化)
|
| 221 |
+
<button onClick={() => { setAutoPlay(false); reset(); }}>
|
| 222 |
+
<RotateCcw size={12} />
|
| 223 |
+
<span>重开</span>
|
| 224 |
+
</button>
|
| 225 |
+
```
|
| 226 |
+
|
| 227 |
+
#### 3.5.2 `Chart.tsx`
|
| 228 |
+
|
| 229 |
+
**功能**:K线图表,使用 `klinecharts` 库。
|
| 230 |
+
|
| 231 |
+
**关键逻辑**:
|
| 232 |
+
- **数据加载**:监听 `currentIndex` 变化,更新图表显示范围
|
| 233 |
+
- **指标计算**:支持 MA, VOL, MACD 等指标
|
| 234 |
+
- **交易标记**:在图表上显示买入/卖出标记
|
| 235 |
+
|
| 236 |
+
#### 3.5.3 `StockHeader.tsx`
|
| 237 |
+
|
| 238 |
+
**功能**:顶部股票信息栏,显示当前股票名称、代码、价格等信息。
|
| 239 |
+
|
| 240 |
+
---
|
| 241 |
+
|
| 242 |
+
## 4. 关键问题与解决方案(基于历史对话)
|
| 243 |
+
|
| 244 |
+
### 4.1 ETF/LOF/REITs 数据缺失
|
| 245 |
+
|
| 246 |
+
**问题描述**:日志显示 `ETF: 1436 / 0`,列表中有数据但日线数据为空
|
| 247 |
+
|
| 248 |
+
**原因分析**:
|
| 249 |
+
1. 使用了不稳定或过时的 Akshare 接口(如 `fund_etf_category_sina`)
|
| 250 |
+
2. REITs 接口返回的字段名(`今开`, `最新价`)与预期不符,导致解析失败
|
| 251 |
+
|
| 252 |
+
**解决方案**:
|
| 253 |
+
1. 切换到稳定的东方财富接口:`fund_etf_spot_em`, `fund_etf_hist_em`, `reits_realtime_em`, `reits_hist_em`
|
| 254 |
+
2. 扩展 `rename_map`,增加 `今开` -> `open`, `最新价` -> `close` 的映射
|
| 255 |
+
|
| 256 |
+
---
|
| 257 |
+
|
| 258 |
+
### 4.2 DuckDB Table/View 冲突
|
| 259 |
+
|
| 260 |
+
**问题描述**:`Catalog Error: Existing object stock_daily is of type Table, trying to replace with type View`
|
| 261 |
+
|
| 262 |
+
**原因分析**:本地 DuckDB 文件中 `stock_daily` 是一个表,但云端模式试图创建同名视图,DuckDB 不允许直接覆盖
|
| 263 |
+
|
| 264 |
+
**解决方案**:在创建视图前,先执行 `DROP VIEW IF EXISTS stock_daily` 和 `DROP TABLE IF EXISTS stock_daily`
|
| 265 |
+
|
| 266 |
+
---
|
| 267 |
+
|
| 268 |
+
### 4.3 数据同步网络超时
|
| 269 |
+
|
| 270 |
+
**问题描述**:大量 `Read timed out` 和 `ConnectionResetError`
|
| 271 |
+
|
| 272 |
+
**原因分析**:并发过高,触发数据源频率限制
|
| 273 |
+
|
| 274 |
+
**解决方案**:
|
| 275 |
+
1. 降低并发数:`MAX_WORKERS` 从 10 降为 5
|
| 276 |
+
2. 增加重试次数:`max_retries` 从 2 增为 3
|
| 277 |
+
|
| 278 |
+
---
|
| 279 |
+
|
| 280 |
+
### 4.4 可转债数据解析失败
|
| 281 |
+
|
| 282 |
+
**问题描述**:`Failed to fetch sh110805 (可转债): 'date'`
|
| 283 |
+
|
| 284 |
+
**原因分析**:部分可转债代码格式或接口返回异常,缺少 `date` 列
|
| 285 |
+
|
| 286 |
+
**解决方案**:
|
| 287 |
+
1. 增加代码格式处理:`cov_symbol = code[-6:] if len(code) > 6 else code`
|
| 288 |
+
2. 增强字段兼容性:尝试将索引转为列 `df.reset_index()`
|
| 289 |
+
3. 静默跳过无效数据,避免日志噪音
|
| 290 |
+
|
| 291 |
+
---
|
| 292 |
+
|
| 293 |
+
### 4.5 重开按钮逻辑优化
|
| 294 |
+
|
| 295 |
+
**需求**:点击"重开"或"再来一局"后,回到首页,不自动开始
|
| 296 |
+
|
| 297 |
+
**解决方案**:修改 `TradePanel.tsx` 中的按钮点击事件,移除 `handleStartGame()` 调用,仅调用 `reset()`
|
| 298 |
+
|
| 299 |
+
**修改前**:
|
| 300 |
+
```typescript
|
| 301 |
+
onClick={() => { reset(); handleStartGame(); }}
|
| 302 |
+
```
|
| 303 |
+
|
| 304 |
+
**修改后**:
|
| 305 |
+
```typescript
|
| 306 |
+
onClick={() => { setAutoPlay(false); reset(); }}
|
| 307 |
+
```
|
| 308 |
+
|
| 309 |
+
---
|
| 310 |
+
|
| 311 |
+
## 5. 部署与运维
|
| 312 |
+
|
| 313 |
+
### 5.1 环境变量
|
| 314 |
+
|
| 315 |
+
| 变量名 | 说明 | 示例 |
|
| 316 |
+
|--------|------|------|
|
| 317 |
+
| `HF_TOKEN` | Hugging Face Token | `hf_xxxxx` |
|
| 318 |
+
| `DATASET_REPO_ID` | HF Dataset 仓库 ID | `superxu520/Paper_Trading_Data` |
|
| 319 |
+
| `DUCKDB_PATH` | 本地 DuckDB 路径(可选) | `/app/data/stock_data.duckdb` |
|
| 320 |
+
|
| 321 |
+
### 5.2 数据同步流程
|
| 322 |
+
|
| 323 |
+
1. 本地配置 `HF_TOKEN` 和 `DATASET_REPO_ID`
|
| 324 |
+
2. 运行 `python backend/scripts/sync_data.py`
|
| 325 |
+
3. 脚本会自动抓取数据、保存为 Parquet、上传至 HF Dataset
|
| 326 |
+
|
| 327 |
+
### 5.3 Hugging Face Spaces 部署
|
| 328 |
+
|
| 329 |
+
- 项目根目录需包含 `Dockerfile`
|
| 330 |
+
- `Dockerfile` 中需设置环境变量、安装依赖、启动命令
|
| 331 |
+
- HF Space 启动后,会自动从 Dataset 下载最新数据
|
| 332 |
+
|
| 333 |
+
---
|
| 334 |
+
|
| 335 |
+
## 6. 未来优化方向
|
| 336 |
+
|
| 337 |
+
1. **数据源稳定性**:寻找更稳定的可转债数据源,或增加多源切换机制
|
| 338 |
+
2. **自动化同步**:使用 GitHub Actions 定时触发 `sync_data.py`,实现每日自动更新
|
| 339 |
+
3. **更多指标**:支持 BOLL, KDJ, RSI 等更多技术指标
|
| 340 |
+
4. **策略回测**:支持用户编写策略脚本进行自动化回测
|
| 341 |
+
5. **用户系统**:增加用户注册、登录,保存历史战绩
|
| 342 |
+
6. **社交分享**:优化分享卡片,支持更多社交平台
|
| 343 |
+
|
| 344 |
+
---
|
| 345 |
+
|
| 346 |
+
## 7. 项目目录结构
|
| 347 |
+
|
| 348 |
+
```
|
| 349 |
+
Paper_Trading/
|
| 350 |
+
├── backend/
|
| 351 |
+
│ ├── app/
|
| 352 |
+
│ │ ├── __init__.py
|
| 353 |
+
│ │ ├── core.py # 游戏核心逻辑
|
| 354 |
+
│ │ ├── database.py # DuckDB 数据库管理
|
| 355 |
+
│ │ └── main.py # FastAPI 主入口
|
| 356 |
+
│ ├── scripts/
|
| 357 |
+
│ │ └── sync_data.py # 数据同步脚本
|
| 358 |
+
│ └── data/ # 本地数据目录
|
| 359 |
+
├── frontend/
|
| 360 |
+
│ ├── src/
|
| 361 |
+
│ │ ├── app/
|
| 362 |
+
│ │ │ ├── page.tsx # 主页面
|
| 363 |
+
│ │ │ └── layout.tsx # 布局组件
|
| 364 |
+
│ │ ├── components/
|
| 365 |
+
│ │ │ ├── TradePanel.tsx # 交易面板
|
| 366 |
+
│ │ │ ├── Chart.tsx # K线图表
|
| 367 |
+
│ │ │ └── StockHeader.tsx # 股票信息栏
|
| 368 |
+
│ │ ├── store/
|
| 369 |
+
│ │ │ └── gameStore.ts # Zustand 状态管理
|
| 370 |
+
│ │ └── lib/
|
| 371 |
+
│ │ ├── api.ts # API 封装
|
| 372 |
+
│ │ └── resample.ts # K线重采样
|
| 373 |
+
│ └── public/ # 静态资源
|
| 374 |
+
├── Dockerfile # HF Spaces 部署配置
|
| 375 |
+
├── README.md # 项目说明
|
| 376 |
+
└── TECHNICAL_DOCUMENTATION.md # 本文档
|
| 377 |
+
```
|
| 378 |
+
|
| 379 |
+
---
|
| 380 |
+
|
| 381 |
+
## 8. 参��资料
|
| 382 |
+
|
| 383 |
+
- [FastAPI 官方文档](https://fastapi.tiangolo.com/)
|
| 384 |
+
- [DuckDB 官方文档](https://duckdb.org/docs/)
|
| 385 |
+
- [Akshare 官方文档](https://akshare.akfamily.xyz/)
|
| 386 |
+
- [Next.js 官方文档](https://nextjs.org/docs)
|
| 387 |
+
- [Zustand 官方文档](https://docs.pmnd.rs/zustand)
|
| 388 |
+
- [Hugging Face Spaces 文档](https://huggingface.co/docs/hub/spaces)
|
| 389 |
+
|
| 390 |
+
---
|
| 391 |
+
|
| 392 |
+
*文档版本:2025.02.18*
|
| 393 |
+
*最后更新:优化重开按钮逻辑、修复 DuckDB 冲突、增强 ETF/LOF/REITs 数据同步*
|