From d649d59c44a21d05a8fe7081e48791efff9c631f Mon Sep 17 00:00:00 2001 From: claude_dev Date: Sat, 15 Aug 2026 08:55:00 +0800 Subject: [PATCH] =?UTF-8?q?feat(paper):=20=E5=8F=8C=E8=BD=A8=E5=AF=B9?= =?UTF-8?q?=E8=B4=A6=E5=89=8D=E7=AB=AF=E9=A1=B5=E2=80=94=E2=80=94=E6=A8=A1?= =?UTF-8?q?=E6=8B=9F=E7=9B=98=E8=8F=9C=E5=8D=95=E6=96=B0=E5=A2=9E=E3=80=8C?= =?UTF-8?q?=E5=8F=8C=E8=BD=A8=E5=AF=B9=E8=B4=A6=E3=80=8D:=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E9=85=8D=E5=AF=B9=E5=8D=A1=E7=89=87(=E7=AD=96?= =?UTF-8?q?=E7=95=A5/=E5=AE=9E=E7=9B=98#vs=E5=BD=B1=E5=AD=90#/=E6=97=A5?= =?UTF-8?q?=E6=9C=9F/PASS=E5=BE=BD=E6=A0=87)+=E5=9B=9B=E6=8C=87=E6=A0=87?= =?UTF-8?q?=E5=8D=A1(=E7=AC=94=E6=95=B0=E5=85=A8=E5=90=8C/=E5=9D=87?= =?UTF-8?q?=E4=BB=B7=E5=B7=AEbps/=E6=8C=81=E4=BB=93=E9=80=90=E5=8F=AA/?= =?UTF-8?q?=E5=87=80=E5=80=BC=E6=9C=88=E5=81=8F=E5=B7=AE,=E5=90=84?= =?UTF-8?q?=E5=B8=A6=E8=BE=BE=E6=A0=87=E5=BE=BD=E6=A0=87)+=E6=88=90?= =?UTF-8?q?=E4=BA=A4=E5=AF=B9=E6=AF=94=E8=A1=A8(symbol+side=E8=81=9A?= =?UTF-8?q?=E5=90=88vwap=E5=B8=A6=E7=AC=A6=E5=8F=B7bps,>10=E6=A0=87?= =?UTF-8?q?=E7=BA=A2)+=E6=8C=81=E4=BB=93=E5=AF=B9=E6=AF=94=E8=A1=A8(?= =?UTF-8?q?=E6=95=B0=E9=87=8F=E5=B7=AE=E2=89=A00=E6=A0=87=E7=BA=A2)+?= =?UTF-8?q?=E6=97=A5=E6=9C=9F=E9=80=89=E6=8B=A9=E6=9F=A5=E8=AF=A2;api=20ge?= =?UTF-8?q?tReconcilePairs+mock=E6=A0=B7=E4=BE=8B(=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E9=A1=BB=E6=B3=A8=E5=86=8C=E5=9C=A8/paper/{aid}=E5=89=8D?= =?UTF-8?q?=E9=98=B2=E5=90=9E);dev=E6=88=AA=E5=9B=BE=E9=AA=8C=E8=AF=81=20[?= =?UTF-8?q?nas]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/api/paper.ts | 57 ++++++++ frontend/src/mock/data.ts | 42 ++++++ frontend/src/mock/index.ts | 1 + frontend/src/router/index.ts | 1 + frontend/src/views/Layout.vue | 3 + frontend/src/views/paper/Reconcile.vue | 176 +++++++++++++++++++++++++ 6 files changed, 280 insertions(+) create mode 100644 frontend/src/views/paper/Reconcile.vue diff --git a/frontend/src/api/paper.ts b/frontend/src/api/paper.ts index 8c8bde6..59a7b89 100644 --- a/frontend/src/api/paper.ts +++ b/frontend/src/api/paper.ts @@ -160,3 +160,60 @@ export async function updatePaper(aid: number, req: PaperUpdateReq): Promise<{ u const { data } = await apiClient.put(`/paper/${aid}`, req) return data } + +// ===== 双轨对账(影子柜台 vs 实盘模拟,设计 §8.2)===== + +export interface ReconcileTradeRow { + symbol: string + side: string + live_volume: number + shadow_volume: number + live_vwap: number | null + shadow_vwap: number | null + price_diff_bps: number | null +} + +export interface ReconcilePositionRow { + symbol: string + live_volume: number + shadow_volume: number + volume_diff: number +} + +export interface ReconcileReport { + date: string + live_account_id: number + shadow_account_id: number + trades: { + live_count: number + shadow_count: number + count_match: boolean + rows: ReconcileTradeRow[] + avg_price_diff_bps: number + pass_price: boolean + } + positions: { rows: ReconcilePositionRow[]; match: boolean } + nav: { + live_total: number | null + shadow_total: number | null + mtd_deviation_pct: number | null + pass_nav: boolean | null + live_mtd_return_pct?: number + shadow_mtd_return_pct?: number + note?: string + } + passed: boolean +} + +export interface ReconcilePair { + live_account_id: number + shadow_account_id: number + strategy: string + report: ReconcileReport +} + +export async function getReconcilePairs(date?: string): Promise { + const { data } = await apiClient.get<{ pairs: ReconcilePair[] }>( + '/paper/reconcile', { params: date ? { date } : {} }) + return data.pairs +} diff --git a/frontend/src/mock/data.ts b/frontend/src/mock/data.ts index f13aa3e..13b1d3a 100644 --- a/frontend/src/mock/data.ts +++ b/frontend/src/mock/data.ts @@ -423,3 +423,45 @@ export const icSummaryMock = { /* ========== 任务状态(progress 轮询) ========== */ export const taskStatusDoneMock = { task_id: 'cta_7a92d1f6', status: 'done', stage: 'compute_metrics' } export const taskStatusRunningMock = { task_id: 'cta_running01', status: 'running', stage: 'run_backtesting' } + +// 双轨对账(影子 vs 实盘,§8.2 四指标样例) +export const reconcileMock = { + pairs: [{ + live_account_id: 5, + shadow_account_id: 39, + strategy: 'channel_test', + report: { + date: '2026-08-17', + live_account_id: 5, + shadow_account_id: 39, + trades: { + live_count: 6, + shadow_count: 6, + count_match: true, + rows: [ + { symbol: '510300', side: 'B', live_volume: 20000, shadow_volume: 20000, live_vwap: 3.986, shadow_vwap: 3.982, price_diff_bps: 1.0 }, + { symbol: '159915', side: 'B', live_volume: 15000, shadow_volume: 15000, live_vwap: 2.114, shadow_vwap: 2.108, price_diff_bps: 2.8 }, + { symbol: '600000', side: 'S', live_volume: 8000, shadow_volume: 8000, live_vwap: 8.912, shadow_vwap: 8.903, price_diff_bps: 1.0 }, + ], + avg_price_diff_bps: 1.6, + pass_price: true, + }, + positions: { + rows: [ + { symbol: '510300', live_volume: 20000, shadow_volume: 20000, volume_diff: 0 }, + { symbol: '159915', live_volume: 15000, shadow_volume: 15000, volume_diff: 0 }, + ], + match: true, + }, + nav: { + live_total: 1004218.5, + shadow_total: 1003886.2, + mtd_deviation_pct: 0.03, + pass_nav: true, + live_mtd_return_pct: 0.42, + shadow_mtd_return_pct: 0.39, + }, + passed: true, + }, + }], +} diff --git a/frontend/src/mock/index.ts b/frontend/src/mock/index.ts index 756e331..e6f3f1a 100644 --- a/frontend/src/mock/index.ts +++ b/frontend/src/mock/index.ts @@ -55,6 +55,7 @@ const routes: Route[] = [ { m: 'get', re: /^\/kline$/, build: () => D.klineMock }, // paper { m: 'get', re: /^\/paper$/, build: () => D.paperListMock }, + { m: 'get', re: /^\/paper\/reconcile/, build: () => D.reconcileMock }, { m: 'get', re: /^\/paper\/[^/]+$/, build: () => D.paperDetailMock }, { m: 'get', re: /^\/paper\/[^/]+\/equity$/, build: () => D.paperEquityMock }, { m: 'get', re: /^\/paper\/[^/]+\/trades$/, build: () => D.paperTradesMock }, diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index 5a85960..4df85cc 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -27,6 +27,7 @@ const routes: RouteRecordRaw[] = [ { path: 'factor/result/:id', name: 'fc-result', component: () => import('@/views/factor/Result.vue') }, { path: 'paper/new', name: 'paper-new', component: () => import('@/views/paper/New.vue') }, { path: 'paper', name: 'paper-list', component: () => import('@/views/paper/List.vue') }, + { path: 'paper/reconcile', name: 'paper-reconcile', component: () => import('@/views/paper/Reconcile.vue') }, { path: 'paper/result/:id', name: 'paper-result', component: () => import('@/views/paper/Result.vue') }, { path: 'paper/live/:aid', name: 'paper-live', component: () => import('@/views/paper/Live.vue') }, { path: 'live/new', name: 'live-new', component: () => import('@/views/live/New.vue') }, diff --git a/frontend/src/views/Layout.vue b/frontend/src/views/Layout.vue index 5cc6cad..8db8fdb 100644 --- a/frontend/src/views/Layout.vue +++ b/frontend/src/views/Layout.vue @@ -34,6 +34,7 @@ const PAGE_TITLE: Array<{ match: RegExp; group: string; title: string }> = [ { match: /^\/paper\/new$/, group: '模拟盘', title: '新建模拟盘' }, { match: /^\/paper\/result\//, group: '模拟盘', title: '模拟盘结果' }, { match: /^\/paper\/live\//, group: '模拟盘', title: '实走监控' }, + { match: /^\/paper\/reconcile/, group: '模拟盘', title: '双轨对账' }, { match: /^\/paper$/, group: '模拟盘', title: '模拟盘' }, { match: /^\/live\/new$/, group: '实盘', title: '新建实盘' }, { match: /^\/live\/monitor\//, group: '实盘', title: '实盘监控' }, @@ -67,6 +68,7 @@ const CMD_ITEMS: CmdItem[] = [ { label: '任务列表', group: '回测', path: '/backtest/history' }, { label: '因子分析', group: '投研', path: '/factor/new' }, { label: '模拟盘列表', group: '模拟', path: '/paper' }, + { label: '双轨对账', group: '模拟', path: '/paper/reconcile' }, { label: '新建模拟盘', group: '模拟', path: '/paper/new' }, { label: '实盘列表', group: '实盘', path: '/live' }, { label: '新建实盘', group: '实盘', path: '/live/new' }, @@ -173,6 +175,7 @@ function onLogout(): void { 模拟盘列表 + 双轨对账 新建模拟盘 diff --git a/frontend/src/views/paper/Reconcile.vue b/frontend/src/views/paper/Reconcile.vue new file mode 100644 index 0000000..4b88bdf --- /dev/null +++ b/frontend/src/views/paper/Reconcile.vue @@ -0,0 +1,176 @@ + + + + +