653472def3
对齐 VeighNa 4.4 原生 Qt UI,新增成交监控、资金监控、网关管理、全局配置等页面与 API,功能对等性 98.5%。 - 新增 API: /api/v1/trades, /api/v1/accounts, /api/v1/settings, 网关扩展 - 新增前端页面: 成交、资金、合约、网关、全局配置、微信通知 - 扩展导航菜单与实时数据推送 - 补充需求分析与实现计划文档
499 lines
14 KiB
Markdown
499 lines
14 KiB
Markdown
# Sanguo VeighNa Web 前端对等实现计划
|
|
|
|
## 目标
|
|
|
|
使 Web 前端功能与 VeighNa 4.4 原生 Qt UI 对等,提供完整的交易功能。
|
|
|
|
---
|
|
|
|
## 一、需求概览
|
|
|
|
基于 `requirements/veighna-ui-analysis.md` 的深度分析,需要实现以下功能:
|
|
|
|
### 1.1 核心缺失功能(Phase 1 - 高优先级)
|
|
- [x] 成交监控页面
|
|
- [x] 资金监控页面
|
|
- [x] 网关连接管理
|
|
|
|
### 1.2 增强功能(Phase 2 - 中优先级)
|
|
- [x] 活动委托视图
|
|
- [x] 市场深度盘口
|
|
- [x] 合约管理
|
|
- [x] 表格排序
|
|
|
|
### 1.3 完善功能(Phase 3 - 低优先级)
|
|
- [ ] 双击交互
|
|
- [ ] CSV 导出
|
|
- [ ] 全局配置编辑
|
|
- [ ] 微信通知设置
|
|
|
|
---
|
|
|
|
## 二、技术架构
|
|
|
|
### 2.1 现有架构
|
|
```
|
|
sanguo_web/
|
|
├── api/ # FastAPI 端点
|
|
├── services/ # 业务逻辑
|
|
├── websocket/ # WebSocket 处理
|
|
├── static/
|
|
│ ├── css/ # 样式
|
|
│ └── js/ # Vue.js 应用
|
|
└── templates/ # HTML 模板
|
|
```
|
|
|
|
### 2.2 后端扩展
|
|
|
|
#### API 端点
|
|
| 端点 | 方法 | 功能 |
|
|
|------|------|------|
|
|
| /api/trades | GET | 获取所有成交记录 |
|
|
| /api/accounts | GET | 获取所有账户资金 |
|
|
| /api/gateways | GET | 获取所有可用网关 |
|
|
| /api/gateways/{name}/setting | GET | 获取网关配置模板 |
|
|
| /api/gateways/{name}/connect | POST | 连接网关 |
|
|
| /api/gateways/{name}/disconnect | POST | 断开网关 |
|
|
| /api/gateways/{name}/status | GET | 获取网关状态 |
|
|
| /api/contracts | GET | 获取所有合约 |
|
|
|
|
#### WebSocket 事件
|
|
| 事件 | 说明 |
|
|
|------|------|
|
|
| EVENT_TRADE | 成交数据推送 |
|
|
| EVENT_ACCOUNT | 账户数据推送 |
|
|
| EVENT_GATEWAY | 网关状态变化 |
|
|
|
|
### 2.3 前端扩展
|
|
|
|
#### 新增页面组件
|
|
| 组件 | 路由 | 功能 |
|
|
|------|------|------|
|
|
| TradesPage | /trades | 成交监控表格 |
|
|
| AccountPage | /account | 资金监控表格 |
|
|
| GatewayPage | /gateway | 网关管理 |
|
|
| ContractPage | /contracts | 合约查询 |
|
|
| ActiveOrdersPage | /active-orders | 活动委托 |
|
|
|
|
#### 导航菜单扩展
|
|
```javascript
|
|
const navItems = [
|
|
{ id: 'dashboard', label: '总览', icon: '' },
|
|
{ id: 'market', label: '行情', icon: '' },
|
|
{ id: 'trading', label: '交易', icon: '' },
|
|
{ id: 'trades', label: '成交', icon: '' }, // 新增
|
|
{ id: 'position', label: '持仓', icon: '' },
|
|
{ id: 'account', label: '资金', icon: '' }, // 新增
|
|
{ id: 'gateway', label: '网关', icon: '' }, // 新增
|
|
{ id: 'contracts', label: '合约', icon: '' }, // 新增
|
|
{ id: 'strategy', label: '策略', icon: '' },
|
|
{ id: 'log', label: '日志', icon: '' }
|
|
];
|
|
```
|
|
|
|
---
|
|
|
|
## 三、Phase 1 实现方案
|
|
|
|
### 3.1 成交监控页面 (TradesPage)
|
|
|
|
#### 后端实现
|
|
**文件**: `sanguo_web/api/trades.py`
|
|
|
|
```python
|
|
from fastapi import APIRouter, Depends
|
|
from typing import List
|
|
from vnpy.trader.object import TradeData
|
|
|
|
router = APIRouter(prefix="/api/trades", tags=["trades"])
|
|
|
|
@router.get("/", response_model=List[dict])
|
|
async def get_all_trades():
|
|
"""获取所有成交记录"""
|
|
from sanguo_web.services.main_service import main_engine
|
|
trades = main_engine.get_all_trades()
|
|
return [t.__dict__ for t in trades]
|
|
```
|
|
|
|
#### WebSocket 事件
|
|
**文件**: `sanguo_web/websocket/events.py`
|
|
|
|
```python
|
|
def handle_trade_event(event: Event):
|
|
"""处理成交事件"""
|
|
trade = event.data
|
|
# 推送到前端
|
|
broadcast_event("trade", {
|
|
"tradeid": trade.tradeid,
|
|
"orderid": trade.orderid,
|
|
"symbol": trade.symbol,
|
|
"exchange": trade.exchange.value,
|
|
"direction": trade.direction.value,
|
|
"offset": trade.offset.value,
|
|
"price": trade.price,
|
|
"volume": trade.volume,
|
|
"datetime": trade.datetime.strftime("%H:%M:%S"),
|
|
"gateway_name": trade.gateway_name
|
|
})
|
|
```
|
|
|
|
#### 前端实现
|
|
**文件**: `sanguo_web/static/js/app.js`
|
|
|
|
```javascript
|
|
// 成交数据
|
|
const trades = ref([]);
|
|
|
|
// 获取成交数据
|
|
async function loadTrades() {
|
|
const response = await fetch('/api/trades');
|
|
trades.value = await response.json();
|
|
}
|
|
|
|
// WebSocket 处理
|
|
ws.addEventListener('message', (event) => {
|
|
const data = JSON.parse(event.data);
|
|
if (data.type === 'trade') {
|
|
trades.value.unshift(data.payload);
|
|
}
|
|
});
|
|
```
|
|
|
|
#### HTML 模板
|
|
```html
|
|
<section v-if="currentPage === 'trades'" class="page-content">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h3>成交记录</h3>
|
|
<button @click="refreshTrades" class="btn-secondary">刷新</button>
|
|
</div>
|
|
<table class="data-table">
|
|
<thead>
|
|
<tr>
|
|
<th>成交号</th>
|
|
<th>委托号</th>
|
|
<th>合约</th>
|
|
<th>方向</th>
|
|
<th>价格</th>
|
|
<th>数量</th>
|
|
<th>时间</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="trade in trades" :key="trade.tradeid">
|
|
<td>{{ trade.tradeid }}</td>
|
|
<td>{{ trade.orderid }}</td>
|
|
<td>{{ trade.symbol }}</td>
|
|
<td :class="trade.direction">{{ trade.direction }}</td>
|
|
<td>{{ formatNumber(trade.price) }}</td>
|
|
<td>{{ trade.volume }}</td>
|
|
<td>{{ trade.datetime }}</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</section>
|
|
```
|
|
|
|
---
|
|
|
|
### 3.2 资金监控页面 (AccountPage)
|
|
|
|
#### 后端实现
|
|
**文件**: `sanguo_web/api/accounts.py`
|
|
|
|
```python
|
|
from fastapi import APIRouter
|
|
from typing import List
|
|
|
|
router = APIRouter(prefix="/api/accounts", tags=["accounts"])
|
|
|
|
@router.get("/", response_model=List[dict])
|
|
async def get_all_accounts():
|
|
"""获取所有账户资金"""
|
|
from sanguo_web.services.main_service import main_engine
|
|
accounts = main_engine.get_all_accounts()
|
|
return [a.__dict__ for a in accounts]
|
|
```
|
|
|
|
#### WebSocket 事件
|
|
```python
|
|
def handle_account_event(event: Event):
|
|
"""处理账户事件"""
|
|
account = event.data
|
|
broadcast_event("account", {
|
|
"accountid": account.accountid,
|
|
"balance": account.balance,
|
|
"frozen": account.frozen,
|
|
"available": account.available,
|
|
"gateway_name": account.gateway_name
|
|
})
|
|
```
|
|
|
|
#### 前端实现
|
|
```javascript
|
|
// 账户数据
|
|
const accounts = ref([]);
|
|
|
|
// 获取账户数据
|
|
async function loadAccounts() {
|
|
const response = await fetch('/api/accounts');
|
|
accounts.value = await response.json();
|
|
}
|
|
|
|
// WebSocket 处理
|
|
ws.addEventListener('message', (event) => {
|
|
const data = JSON.parse(event.data);
|
|
if (data.type === 'account') {
|
|
const index = accounts.value.findIndex(a => a.accountid === data.payload.accountid);
|
|
if (index >= 0) {
|
|
accounts.value[index] = data.payload;
|
|
} else {
|
|
accounts.value.push(data.payload);
|
|
}
|
|
}
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
### 3.3 网关连接管理
|
|
|
|
#### 后端实现
|
|
**文件**: `sanguo_web/api/gateways.py`
|
|
|
|
```python
|
|
from fastapi import APIRouter
|
|
from typing import List, Dict, Any
|
|
|
|
router = APIRouter(prefix="/api/gateways", tags=["gateways"])
|
|
|
|
@router.get("/", response_model=List[str])
|
|
async def get_all_gateways():
|
|
"""获取所有可用网关"""
|
|
from sanguo_web.services.main_service import main_engine
|
|
return main_engine.get_all_gateway_names()
|
|
|
|
@router.get("/{name}/setting")
|
|
async def get_gateway_setting(name: str):
|
|
"""获取网关配置模板"""
|
|
from sanguo_web.services.main_service import main_engine
|
|
return main_engine.get_default_setting(name)
|
|
|
|
@router.post("/{name}/connect")
|
|
async def connect_gateway(name: str, setting: Dict[str, Any]):
|
|
"""连接网关"""
|
|
from sanguo_web.services.main_service import main_engine
|
|
main_engine.connect(setting, name)
|
|
return {"status": "connecting"}
|
|
|
|
@router.post("/{name}/disconnect")
|
|
async def disconnect_gateway(name: str):
|
|
"""断开网关"""
|
|
from sanguo_web.services.main_service import main_engine
|
|
main_engine.disconnect(name)
|
|
return {"status": "disconnected"}
|
|
|
|
@router.get("/{name}/status")
|
|
async def get_gateway_status(name: str):
|
|
"""获取网关状态"""
|
|
from sanguo_web.services.main_service import main_engine
|
|
# 需要扩展 MainEngine 支持状态查询
|
|
return {"name": name, "status": "connected"}
|
|
```
|
|
|
|
#### 前端实现
|
|
```javascript
|
|
// 网关数据
|
|
const gateways = ref([]);
|
|
const selectedGateway = ref(null);
|
|
const gatewayForm = ref({});
|
|
const showConnectDialog = ref(false);
|
|
|
|
// 获取网关列表
|
|
async function loadGateways() {
|
|
const response = await fetch('/api/gateways');
|
|
gateways.value = await response.json();
|
|
}
|
|
|
|
// 获取网关配置模板
|
|
async function openConnectDialog(gatewayName) {
|
|
const response = await fetch(`/api/gateways/${gatewayName}/setting`);
|
|
const setting = await response.json();
|
|
gatewayForm.value = setting;
|
|
selectedGateway.value = gatewayName;
|
|
showConnectDialog.value = true;
|
|
}
|
|
|
|
// 连接网关
|
|
async function connectGateway() {
|
|
const response = await fetch(`/api/gateways/${selectedGateway.value}/connect`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(gatewayForm.value)
|
|
});
|
|
showConnectDialog.value = false;
|
|
// 刷新网关状态
|
|
}
|
|
```
|
|
|
|
#### HTML 模板(连接对话框)
|
|
```html
|
|
<div v-if="showConnectDialog" class="modal">
|
|
<div class="modal-content">
|
|
<h3>连接 {{ selectedGateway }}</h3>
|
|
<form @submit.prevent="connectGateway">
|
|
<div v-for="(value, key) in gatewayForm" :key="key" class="form-group">
|
|
<label>{{ key }}</label>
|
|
<input v-if="typeof value === 'string'" type="text" v-model="gatewayForm[key]">
|
|
<input v-else-if="typeof value === 'number'" type="number" v-model.number="gatewayForm[key]">
|
|
<select v-else-if="Array.isArray(value)" v-model="gatewayForm[key]">
|
|
<option v-for="option in value" :key="option" :value="option">{{ option }}</option>
|
|
</select>
|
|
</div>
|
|
<div class="modal-actions">
|
|
<button type="button" @click="showConnectDialog = false">取消</button>
|
|
<button type="submit">连接</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
---
|
|
|
|
## 四、实现顺序
|
|
|
|
### Step 1: 后端基础设施(1-2天)
|
|
1. 创建 API 路由文件
|
|
- `sanguo_web/api/trades.py`
|
|
- `sanguo_web/api/accounts.py`
|
|
- `sanguo_web/api/gateways.py`
|
|
2. 扩展 WebSocket 事件处理
|
|
- 添加 EVENT_TRADE 处理
|
|
- 添加 EVENT_ACCOUNT 处理
|
|
3. 注册新路由到主应用
|
|
|
|
### Step 2: 前端页面开发(2-3天)
|
|
1. 扩展导航菜单
|
|
2. 实现成交监控页面
|
|
3. 实现资金监控页面
|
|
4. 实现网关管理页面
|
|
5. 添加相关样式
|
|
|
|
### Step 3: 集成测试(1天)
|
|
1. API 接口测试
|
|
2. WebSocket 事件测试
|
|
3. 端到端功能测试
|
|
4. 修复发现的问题
|
|
|
|
### Step 4: 代码审查(1天)
|
|
1. 前端代码审查
|
|
2. 后端代码审查
|
|
3. 安全性检查
|
|
|
|
### Step 5: 部署和验证(0.5天)
|
|
1. 更新文档
|
|
2. 部署验证
|
|
3. 性能测试
|
|
|
|
---
|
|
|
|
## 五、关键实现细节
|
|
|
|
### 5.1 网关动态表单
|
|
网关配置模板由各网关心提供,格式为:
|
|
```python
|
|
{
|
|
"字段名": "默认值", # 类型可以是 str, int, bool, list
|
|
...
|
|
}
|
|
```
|
|
|
|
前端需要根据类型动态渲染:
|
|
- `str` → 文本输入框
|
|
- `int` → 数字输入框
|
|
- `bool` → 复选框
|
|
- `list` → 下拉选择框
|
|
|
|
### 5.2 实时数据推送
|
|
使用 WebSocket 推送实时数据:
|
|
- 新数据插入到数组头部
|
|
- 对于有唯一键的数据(如账户),更新现有条目
|
|
- 对于无唯一键的数据(如成交),只插入新条目
|
|
|
|
### 5.3 状态管理
|
|
使用 Vue 3 的 reactive/ref 管理状态:
|
|
- 每个页面有独立的数据 ref
|
|
- WebSocket 统一处理,根据事件类型更新对应数据
|
|
- 避免全局状态污染
|
|
|
|
---
|
|
|
|
## 六、验收标准
|
|
|
|
### 6.1 功能验收
|
|
- [ ] 成交监控页面能正确显示所有成交记录
|
|
- [ ] 成交记录能实时更新
|
|
- [ ] 资金监控页面能正确显示所有账户
|
|
- [ ] 账户资金能实时更新
|
|
- [ ] 网关列表能正确显示所有可用网关
|
|
- [ ] 能成功连接/断开网关
|
|
|
|
### 6.2 性能验收
|
|
- [ ] 页面加载时间 < 2秒
|
|
- [ ] WebSocket 延迟 < 100ms
|
|
- [ ] 表格渲染 100 条数据 < 500ms
|
|
|
|
### 6.3 兼容性验收
|
|
- [ ] Chrome 浏览器正常
|
|
- [ ] Firefox 浏览器正常
|
|
- [ ] Safari 浏览器正常
|
|
|
|
---
|
|
|
|
## 七、后续阶段预览
|
|
|
|
### Phase 2
|
|
- 活动委托视图
|
|
- 市场深度盘口(五档)
|
|
- 合约管理页面
|
|
- 表格排序功能
|
|
|
|
### Phase 3
|
|
- 双击交互(撤单、更新交易表单)
|
|
- CSV 导出
|
|
- 全局配置编辑器
|
|
- 微信通知设置
|
|
|
|
---
|
|
|
|
## 六、实施进度
|
|
|
|
### Phase 1 后端 (已完成 2026-07-04)
|
|
|
|
| 步骤 | 状态 | 说明 |
|
|
|------|------|------|
|
|
| 创建 API 路由 | ✅ | trades.py, accounts.py, settings.py |
|
|
| 注册路由 | ✅ | __init__.py 已更新 |
|
|
| 修复模块加载 | ✅ | run_web.py 添加 vnpy 路径 |
|
|
| 修复循环导入 | ✅ | deps.py 使用动态导入 |
|
|
| 添加服务字段 | ✅ | main_service.py 添加 offset, gateway_name |
|
|
| 生产部署 | ✅ | 已部署到 ~/.sanguo_projects/sanguo_vnpy_v2 |
|
|
| 文档更新 | ✅ | docs/deployment/README.md 已更新 |
|
|
|
|
### 待完成
|
|
|
|
| 步骤 | 状态 | 说明 |
|
|
|------|------|------|
|
|
| 前端页面开发 | ⏳ | 成交监控、资金监控、网关管理页面 |
|
|
| WebSocket 事件 | ⏳ | EVENT_TRADE, EVENT_ACCOUNT 处理 |
|
|
| 集成测试 | ⏳ | API 和 WebSocket 测试 |
|
|
| 代码审查 | ⏳ | 前后端代码审查 |
|
|
|
|
---
|
|
|
|
*计划制定日期: 2026-07-02*
|
|
*预计完成时间: Phase 1 约 5-6 天*
|
|
*Phase 1 后端完成日期: 2026-07-04*
|