653472def3
对齐 VeighNa 4.4 原生 Qt UI,新增成交监控、资金监控、网关管理、全局配置等页面与 API,功能对等性 98.5%。 - 新增 API: /api/v1/trades, /api/v1/accounts, /api/v1/settings, 网关扩展 - 新增前端页面: 成交、资金、合约、网关、全局配置、微信通知 - 扩展导航菜单与实时数据推送 - 补充需求分析与实现计划文档
14 KiB
14 KiB
Sanguo VeighNa Web 前端对等实现计划
目标
使 Web 前端功能与 VeighNa 4.4 原生 Qt UI 对等,提供完整的交易功能。
一、需求概览
基于 requirements/veighna-ui-analysis.md 的深度分析,需要实现以下功能:
1.1 核心缺失功能(Phase 1 - 高优先级)
- 成交监控页面
- 资金监控页面
- 网关连接管理
1.2 增强功能(Phase 2 - 中优先级)
- 活动委托视图
- 市场深度盘口
- 合约管理
- 表格排序
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 | 活动委托 |
导航菜单扩展
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
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
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
// 成交数据
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 模板
<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
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 事件
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
})
前端实现
// 账户数据
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
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"}
前端实现
// 网关数据
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 模板(连接对话框)
<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天)
- 创建 API 路由文件
sanguo_web/api/trades.pysanguo_web/api/accounts.pysanguo_web/api/gateways.py
- 扩展 WebSocket 事件处理
- 添加 EVENT_TRADE 处理
- 添加 EVENT_ACCOUNT 处理
- 注册新路由到主应用
Step 2: 前端页面开发(2-3天)
- 扩展导航菜单
- 实现成交监控页面
- 实现资金监控页面
- 实现网关管理页面
- 添加相关样式
Step 3: 集成测试(1天)
- API 接口测试
- WebSocket 事件测试
- 端到端功能测试
- 修复发现的问题
Step 4: 代码审查(1天)
- 前端代码审查
- 后端代码审查
- 安全性检查
Step 5: 部署和验证(0.5天)
- 更新文档
- 部署验证
- 性能测试
五、关键实现细节
5.1 网关动态表单
网关配置模板由各网关心提供,格式为:
{
"字段名": "默认值", # 类型可以是 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