From 36db2ed5a65b21da51b87edc9b8a9f50896d19f2 Mon Sep 17 00:00:00 2001 From: claude_dev Date: Fri, 14 Aug 2026 14:32:52 +0800 Subject: [PATCH] =?UTF-8?q?feat(shadow-desk):=20=E9=80=9A=E8=B7=AF?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E7=AD=96=E7=95=A5channel=5Ftest+=E6=A8=A1?= =?UTF-8?q?=E6=8B=9F=E7=9B=98=E5=88=97=E8=A1=A8=E6=94=B9=E9=80=A0:=20?= =?UTF-8?q?=E7=AD=96=E7=95=A5=3D=E6=AF=8F=E6=97=A5=E7=AD=89=E6=9D=83?= =?UTF-8?q?=E8=BD=AE=E6=8D=A2ETF/=E8=93=9D=E7=AD=B9(=E5=8D=96=E6=97=A7?= =?UTF-8?q?=E4=B9=B0=E6=96=B0=E8=B5=B0=E5=85=A8=E4=B9=B0=E5=8D=96=E9=80=9A?= =?UTF-8?q?=E8=B7=AF)+T+1=E5=BD=93=E6=97=A5=E5=8D=96=E6=8E=A2=E9=92=88(?= =?UTF-8?q?=E4=B8=A4=E7=AB=AF=E5=BA=94=E4=B8=80=E8=87=B4=E6=8B=92=E5=8D=95?= =?UTF-8?q?,=E9=AA=8C=E8=AF=81=E6=8B=92=E5=8D=95=E9=80=9A=E8=B7=AF),?= =?UTF-8?q?=E6=B3=A8=E5=86=8Cbacktest/live/=E5=89=8D=E7=AB=AF,=E9=BB=98?= =?UTF-8?q?=E8=AE=A4universe=205=E5=8F=AA=E9=AB=98=E6=B5=81=E5=8A=A8ETF,4?= =?UTF-8?q?=E5=8D=95=E6=B5=8B(=E5=9D=91:BrokerFacade=E6=98=AFdataclass,?= =?UTF-8?q?=E5=AD=90=E7=B1=BB=E6=96=B9=E6=B3=95=E8=A2=AB=E7=88=B6=E7=B1=BB?= =?UTF-8?q?=5F=5Finit=5F=5F=E5=AE=9E=E4=BE=8B=E5=B1=9E=E6=80=A7=E9=81=AE?= =?UTF-8?q?=E8=94=BD=E9=A1=BB=E5=AE=9E=E4=BE=8B=E6=B3=A8=E5=85=A5);=20?= =?UTF-8?q?=E6=A8=A1=E6=8B=9F=E7=9B=98=E5=88=97=E8=A1=A8:=E5=8A=A0?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E5=88=97(=E4=B8=AA=E8=82=A1/=E7=BB=84?= =?UTF-8?q?=E5=90=88)+=E5=88=9B=E5=BB=BA=E6=97=B6=E9=97=B4=E5=88=97,?= =?UTF-8?q?=E6=A0=87=E7=9A=84=E5=8E=BBJSON=E4=B8=AD=E6=8B=AC=E5=8F=B7,?= =?UTF-8?q?=E5=88=97=E5=AE=BD=E9=87=8D=E6=8E=92,=E6=A8=A1=E5=BC=8F?= =?UTF-8?q?=E7=AD=9B=E9=80=89=E5=8A=A0=E5=BD=B1=E5=AD=90=20[vps]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/api/paper.ts | 1 + frontend/src/styles/chips.css | 5 + .../src/views/backtest/PortfolioBacktest.vue | 1 + frontend/src/views/paper/List.vue | 54 +++++--- frontend/src/views/paper/New.vue | 1 + sanguo_portfolio/live_strategy.py | 3 + sanguo_portfolio/runner_backtest.py | 7 +- sanguo_portfolio/strategies/__init__.py | 3 + sanguo_portfolio/strategies/channel_test.py | 116 ++++++++++++++++++ tests/portfolio/test_channel_test.py | 86 +++++++++++++ 10 files changed, 256 insertions(+), 21 deletions(-) create mode 100644 sanguo_portfolio/strategies/channel_test.py create mode 100644 tests/portfolio/test_channel_test.py diff --git a/frontend/src/api/paper.ts b/frontend/src/api/paper.ts index 13fb091..8c8bde6 100644 --- a/frontend/src/api/paper.ts +++ b/frontend/src/api/paper.ts @@ -37,6 +37,7 @@ export interface PaperAccount { initial_capital?: number start_date?: string end_date?: string + created_at?: string last_run_date?: string | null next_run_at?: string | null checkpoint_date?: string | null diff --git a/frontend/src/styles/chips.css b/frontend/src/styles/chips.css index 859338d..7fecc3d 100644 --- a/frontend/src/styles/chips.css +++ b/frontend/src/styles/chips.css @@ -40,6 +40,11 @@ border-color: rgba(0, 229, 255, 0.3); margin-left: 4px; } +.chip-cta { + color: #6ee7a8; + background: rgba(110, 231, 168, 0.1); + border-color: rgba(110, 231, 168, 0.3); +} .chip-portfolio { color: var(--amber); background: rgba(255, 176, 0, 0.08); diff --git a/frontend/src/views/backtest/PortfolioBacktest.vue b/frontend/src/views/backtest/PortfolioBacktest.vue index 8f420bd..e3983f1 100644 --- a/frontend/src/views/backtest/PortfolioBacktest.vue +++ b/frontend/src/views/backtest/PortfolioBacktest.vue @@ -83,6 +83,7 @@ const strategyOptions = [ { label: '牛熊动量', value: 'momentum_timing' }, { label: '价值精选', value: 'value_selection' }, { label: '小市值轮动', value: 'small_cap' }, + { label: '通路测试(影子vs实盘双轨)', value: 'channel_test' }, ] const BENCHMARK_OPTIONS = [ { label: '沪深300', value: '000300.XSHG' }, diff --git a/frontend/src/views/paper/List.vue b/frontend/src/views/paper/List.vue index 8a3633a..334a1f6 100644 --- a/frontend/src/views/paper/List.vue +++ b/frontend/src/views/paper/List.vue @@ -57,11 +57,22 @@ function num(v: number | null | undefined): string { } function symbolsShort(s: string | undefined): string { if (!s) return '—' - const arr = typeof s === 'string' ? s.split(',') : [] - return arr.length > 3 ? arr.slice(0, 3).join(',') + ` +${arr.length - 3}` : s + let arr: string[] = [] + try { + arr = JSON.parse(s) + } catch { + arr = String(s).split(',').map((x) => x.trim()) + } + if (!arr.length) return '—' + return arr.length > 3 ? arr.slice(0, 3).join(',') + ` +${arr.length - 3}` : arr.join(',') +} +const typeLabel: Record = { cta: '个股', portfolio: '组合' } +function createdShort(v: string | undefined): string { + if (!v) return '—' + return v.replace('T', ' ').slice(0, 16) } function open(a: PaperAccount): void { - router.push(a.mode === 'live' ? `/paper/live/${a.id}` : `/paper/result/${a.id}`) + router.push(a.mode === 'replay' ? `/paper/result/${a.id}` : `/paper/live/${a.id}`) } function goNew(): void { router.push('/paper/new') @@ -170,6 +181,7 @@ async function saveEdit(): Promise { + @@ -181,51 +193,55 @@ async function saveEdit(): Promise { - + - + + + + - - + + - - - - + - + - + - - + + - + - +