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:
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user