feat: 实盘模拟(live) + 组合回测MVP(portfolio)

[live] 实盘模拟 vnpy+miniQMT 直连(supervisor 轮询, 前后端):
- sanguo_live: LiveTradingEngine + AShareCtaTemplate(定寸/禁做空) + runner_supervisor(DB驱动) + persistence(4表WAL)
- sanguo_api/routes_live: 9路由(create/start/stop/positions/trades/account/status)
- frontend live: New/List/Monitor + api/live.ts; config/live.yaml

[portfolio] 组合回测 MVP(BulletTrade, 链路代码完成待验证):
- runner_backtest 加 JSON 入口(--json, BacktestEngine 顶层 import)
- sanguo_api/routes_portfolio: POST /portfolio/backtest SSH 触发 VPS 跑
- frontend PortfolioBacktest.vue + api/portfolio.ts: 表单+结果+净值曲线
- 路由/菜单注册(/backtest/portfolio 组合回测)
- 已知: MVP 链路未端到端验证, agent 改至中途被停; 待 Mac 起服务联调
This commit is contained in:
2026-07-18 20:04:16 +08:00
parent a68cf4905e
commit 96b1924fd5
24 changed files with 3550 additions and 9 deletions
+125
View File
@@ -0,0 +1,125 @@
import { apiClient } from './client'
/** 实盘模拟账户(API 返回行) */
export interface LiveAccount {
id: number
name: string
account: string
vt_symbol: string
strategy_class: string
strategy_name: string
/** JSON 字符串,前端 JSON.parse 得到策略参数 */
setting: string
status: string
interval: string
initial_capital: number
connect_wait_sec?: number
init_wait_sec?: number
mini_path?: string
error_msg?: string | null
created_at?: string
updated_at?: string
/** 列表端点附带(单账户 GET 不含) */
latest_equity?: number | null
latest_date?: string | null
total_return?: number | null
position_count?: number
}
export interface LiveCreateRequest {
name: string
account: string
vt_symbol: string
strategy_class: string
strategy_name: string
setting: Record<string, unknown>
interval: string
initial_capital: number
connect_wait_sec?: number
init_wait_sec?: number
mini_path?: string
}
export interface LiveStatus {
account_id: number
status: string
name: string
account: string
vt_symbol: string
strategy_name: string
updated_at?: string
error_msg?: string
}
export interface LivePosition {
symbol: string
volume: number
frozen: number
avg_price: number
updated_at?: string
}
export interface LiveTrade {
account_id: number
strategy_name: string
symbol: string
direction: string
offset: string
price: number
volume: number
traded_at: string
vt_tradeid?: string
}
export interface LiveBalance {
account_id?: number
date?: string
cash?: number
market_value?: number
total?: number
}
export async function createLive(req: LiveCreateRequest): Promise<{ accountId: number; status: string }> {
const { data } = await apiClient.post<{ account_id: number; status: string }>('/live/create', req)
return { accountId: data.account_id, status: data.status }
}
export async function listLives(): Promise<LiveAccount[]> {
const { data } = await apiClient.get<{ accounts: LiveAccount[] }>('/live')
return data.accounts
}
export async function getLive(aid: number): Promise<LiveAccount> {
const { data } = await apiClient.get<LiveAccount>(`/live/${aid}`)
return data
}
export async function startLive(aid: number): Promise<{ accountId: number; status: string }> {
const { data } = await apiClient.post<{ account_id: number; status: string }>(`/live/${aid}/start`)
return { accountId: data.account_id, status: data.status }
}
export async function stopLive(aid: number): Promise<{ accountId: number; status: string }> {
const { data } = await apiClient.post<{ account_id: number; status: string }>(`/live/${aid}/stop`)
return { accountId: data.account_id, status: data.status }
}
export async function getLivePositions(aid: number): Promise<LivePosition[]> {
const { data } = await apiClient.get<LivePosition[]>(`/live/${aid}/positions`)
return data
}
export async function getLiveTrades(aid: number): Promise<LiveTrade[]> {
const { data } = await apiClient.get<LiveTrade[]>(`/live/${aid}/trades`)
return data
}
export async function getLiveAccountBalance(aid: number): Promise<LiveBalance> {
const { data } = await apiClient.get<LiveBalance>(`/live/${aid}/account`)
return data ?? {}
}
export async function getLiveStatus(aid: number): Promise<LiveStatus> {
const { data } = await apiClient.get<LiveStatus>(`/live/${aid}/status`)
return data
}
+68
View File
@@ -0,0 +1,68 @@
import { apiClient } from './client'
export interface PortfolioBacktestReq {
pool: string
start_date: string
end_date: string
initial_cash: number
benchmark?: string
}
export interface EquityPoint {
date: string
equity: number
}
export interface StockPicked {
code: string
name: string
amount: number
avg_cost: number
price: number
value: number
}
export interface PortfolioTrade {
datetime?: string
date?: string
code?: string
side?: string
action?: string
amount?: number
filled_amount?: number
price?: number
filled_price?: number
commission?: number
status?: string
}
export interface PortfolioMetrics {
total_return: number | null
annual_return: number | null
max_drawdown: number | null
sharpe: number | null
win_rate_daily: number | null
win_rate_trade: number | null
trading_days: number | null
}
export interface PortfolioBacktestResult {
strategy: string
period: { start: string; end: string; trading_days: number }
stocks_selected: StockPicked[]
trades: PortfolioTrade[]
equity_curve: EquityPoint[]
metrics: PortfolioMetrics
raw_summary?: Record<string, unknown>
}
export async function postPortfolioBacktest(
req: PortfolioBacktestReq,
): Promise<PortfolioBacktestResult> {
const { data } = await apiClient.post<PortfolioBacktestResult>(
'/portfolio/backtest',
req,
{ timeout: 600000 },
)
return data
}