From 6ecdaab959654d65b7a63baf3bdf8da2758593d3 Mon Sep 17 00:00:00 2001 From: claude_dev Date: Sun, 16 Aug 2026 22:29:29 +0800 Subject: [PATCH] =?UTF-8?q?feat(front+api):=20#79+#94+#95=E9=AA=8C?= =?UTF-8?q?=E6=94=B6=E4=B8=89=E8=BF=9E=E2=80=94=E2=80=94#79:=E7=AD=96?= =?UTF-8?q?=E7=95=A5=E5=BA=93=E5=8D=A1=E7=89=87=E6=94=B6=E7=9B=8A=E6=A0=BC?= =?UTF-8?q?=E7=A7=BB=E9=99=A4(=E7=94=A8=E6=88=B7'=E5=9B=9E=E6=B5=8B=C2=B7?= =?UTF-8?q?=E6=9C=80=E6=96=B010.44%=E6=B2=A1=E5=95=A5=E7=94=A8',=E5=9B=9E?= =?UTF-8?q?=E6=B5=8B=E6=95=B0=E5=AD=97=E5=8F=A3=E5=BE=84=E6=98=93=E8=AF=AF?= =?UTF-8?q?=E5=AF=BC,=E6=94=B6=E7=9B=8A=E7=9C=8B=E5=90=84=E5=88=97?= =?UTF-8?q?=E8=A1=A8/=E5=85=A8=E6=99=AF;latestRet/retInfoOf=E5=AD=A4?= =?UTF-8?q?=E5=84=BF=E5=87=BD=E6=95=B0=E9=9A=8F=E5=88=A0,pct/retClass?= =?UTF-8?q?=E5=B7=A1=E6=A3=80=E4=BB=8D=E5=9C=A8);#94:=E5=AE=9E=E7=9B=98?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E6=A0=87=E7=9A=84=E5=88=97=E6=8E=A5poolLabel?= =?UTF-8?q?(all=E2=86=92=E5=85=A8=E5=B8=82=E5=9C=BA)+=E5=AE=9E=E4=BE=8B?= =?UTF-8?q?=E5=88=97=E5=BB=BAinstanceNames=E6=98=A0=E5=B0=84=E6=98=BE?= =?UTF-8?q?=E5=AE=9E=E4=BE=8B=E5=90=8D(=E5=8E=9F=E7=BB=91row.instance?= =?UTF-8?q?=E4=BD=86=E5=90=8E=E7=AB=AFlist=5Flives=E4=BB=8E=E6=9C=AA?= =?UTF-8?q?=E5=A1=AB=E6=81=92'=E2=80=94',=E5=AF=B9=E9=BD=90=E6=A8=A1?= =?UTF-8?q?=E6=8B=9F=E7=9B=98#71=E5=8F=A3=E5=BE=84,=E5=85=9C=E5=BA=95?= =?UTF-8?q?=E5=AE=9E=E4=BE=8B#id);#95:=E6=A0=87=E7=9A=84=E6=B1=A0=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=E5=80=BC=E7=BB=9F=E4=B8=80=E6=94=B9all=E5=85=A8?= =?UTF-8?q?=E5=B8=82=E5=9C=BA=E2=80=94=E2=80=94=E5=89=8D=E7=AB=AF=E4=B8=89?= =?UTF-8?q?=E8=A1=A8=E5=8D=95(paper/live/PortfolioBacktest=20poolForm)+?= =?UTF-8?q?=E5=90=8E=E7=AB=AF=E5=9B=9B=E5=A4=84=E9=BB=98=E8=AE=A4(routes?= =?UTF-8?q?=5Flive=20setdefault/routes=5Fportfolio=20Field/routes=5Fpaper/?= =?UTF-8?q?orchestrator=20runner)=E5=90=8C=E6=AD=A5=E5=AF=B9=E9=BD=90?= =?UTF-8?q?=E9=98=B2=E5=8F=A3=E5=BE=84=E5=88=86=E8=A3=82,=E6=A0=87?= =?UTF-8?q?=E7=AD=BE'=E5=85=A8=E5=B8=82=E5=9C=BA(=E6=85=A2,=E9=9D=9EMVP)'?= =?UTF-8?q?=E7=AE=80=E5=8C=96'=E5=85=A8=E5=B8=82=E5=9C=BA';936=E7=BB=BF+bu?= =?UTF-8?q?ild=E7=BB=BF=20[vps]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/views/backtest/PortfolioBacktest.vue | 4 ++-- frontend/src/views/live/List.vue | 23 ++++++++++++++++--- frontend/src/views/live/New.vue | 4 ++-- frontend/src/views/paper/New.vue | 4 ++-- frontend/src/views/strategy/List.vue | 20 +--------------- sanguo_api/routes_live.py | 2 +- sanguo_api/routes_paper.py | 2 +- sanguo_api/routes_portfolio.py | 2 +- sanguo_orchestrator/runner.py | 2 +- 9 files changed, 31 insertions(+), 32 deletions(-) diff --git a/frontend/src/views/backtest/PortfolioBacktest.vue b/frontend/src/views/backtest/PortfolioBacktest.vue index afa5d8a..71bb8bd 100644 --- a/frontend/src/views/backtest/PortfolioBacktest.vue +++ b/frontend/src/views/backtest/PortfolioBacktest.vue @@ -55,7 +55,7 @@ const dr = loadDateRange() // §12.6 选档案(跳转带入或页面下拉),提交带 instance_id 回写四格 const selectedInstanceId = ref(null) const form = reactive({ - pool: 'hs300_subset', + pool: 'all', strategy: 'all_weather', max_pool: 30, start: dr.start, @@ -81,7 +81,7 @@ watch( const poolOptions = [ { label: 'HS300 子集(小范围验证)', value: 'hs300_subset' }, - { label: '全市场(慢,非 MVP)', value: 'all' }, + { label: '全市场', value: 'all' }, ] const strategyOptions = [ { label: '全天候轮动', value: 'all_weather' }, diff --git a/frontend/src/views/live/List.vue b/frontend/src/views/live/List.vue index 6679869..d73cc6a 100644 --- a/frontend/src/views/live/List.vue +++ b/frontend/src/views/live/List.vue @@ -2,6 +2,8 @@ import { ref, reactive, computed, onMounted, onUnmounted } from 'vue' import { useRouter, useRoute } from 'vue-router' import CodeSnapshotDrawer from '@/views/strategy/CodeSnapshotDrawer.vue' +import { poolLabel } from '@/constants/strategy' +import { getInstances } from '@/api/strategy' import { ElMessage, ElMessageBox } from 'element-plus' import { listLives, startLive, stopLive, deleteLive, updateLive, @@ -73,6 +75,21 @@ function num(v: number | null | undefined): string { return Math.round(v).toLocaleString('zh-CN') } +// #94 实例名映射(instance_id → 实例名,对齐模拟盘列表口径;后端 list_lives 不填 instance 字段) +const instanceNames = ref>({}) +onMounted(async () => { + try { + const ins = await getInstances() + instanceNames.value = Object.fromEntries(ins.map((i) => [i.id, i.name])) + } catch { + /* 映射失败退化为 实例 #id */ + } +}) +function instanceNameOf(instanceId: number | null | undefined): string { + if (!instanceId) return '—' + return instanceNames.value[instanceId] || `实例 #${instanceId}` +} + function goMonitor(a: LiveAccount): void { router.push(`/live/monitor/${a.id}`) } @@ -253,7 +270,7 @@ async function onStop(a: LiveAccount): Promise { - + - - + + diff --git a/frontend/src/views/live/New.vue b/frontend/src/views/live/New.vue index 5081a2f..a8d10dd 100644 --- a/frontend/src/views/live/New.vue +++ b/frontend/src/views/live/New.vue @@ -19,10 +19,10 @@ const isPortfolio = computed(() => strategyType.value === 'portfolio') const portfolioOptions = ref([]) const portfolioStrategy = ref('all_weather') -const poolForm = ref({ pool: 'hs300_subset', max_pool: 30, benchmark: '000300.XSHG' }) +const poolForm = ref({ pool: 'all', max_pool: 30, benchmark: '000300.XSHG' }) const POOL_OPTIONS = [ { label: 'HS300 子集(小范围验证)', value: 'hs300_subset' }, - { label: '全市场(慢,非 MVP)', value: 'all' }, + { label: '全市场', value: 'all' }, ] const BENCH_OPTIONS = [ { label: '沪深300', value: '000300.XSHG' }, diff --git a/frontend/src/views/paper/New.vue b/frontend/src/views/paper/New.vue index 435ed80..bbe5088 100644 --- a/frontend/src/views/paper/New.vue +++ b/frontend/src/views/paper/New.vue @@ -27,10 +27,10 @@ const instancesOfCta = computed(() => { const file = classToFile.value[form.value.strategies[0]?.name] return file ? instanceOptions.value.filter((i) => i.code_file === file) : instanceOptions.value }) -const poolForm = reactive({ pool: 'hs300_subset', max_pool: 30, benchmark: '000300.XSHG' }) +const poolForm = reactive({ pool: 'all', max_pool: 30, benchmark: '000300.XSHG' }) const POOL_OPTIONS = [ { label: 'HS300 子集(小范围验证)', value: 'hs300_subset' }, - { label: '全市场(慢,非 MVP)', value: 'all' }, + { label: '全市场', value: 'all' }, ] const BENCH_OPTIONS = [ { label: '沪深300', value: '000300.XSHG' }, diff --git a/frontend/src/views/strategy/List.vue b/frontend/src/views/strategy/List.vue index f4c5520..408fd06 100644 --- a/frontend/src/views/strategy/List.vue +++ b/frontend/src/views/strategy/List.vue @@ -115,23 +115,6 @@ const lampOf = (s: string): string => { } const pct = (v: number | null | undefined): string => (v == null ? '—' : (v * 100).toFixed(2) + '%') const retClass = (v: number | null | undefined): string => (v == null ? '' : v >= 0 ? 'up' : 'down') -// #75 收益数值与标签同源:运行类 ret 优先(含其类型),否则回测/回放 run_returns -interface RetInfo { v: number | null; label: string } -function retInfoOf(i: Instance): RetInfo { - const runs = (i.running_accounts || []).filter((a) => a.ret != null) - if (runs.length) { - const last = runs[runs.length - 1] - const label = last.kind === 'live' ? '实盘 · 最新' : last.kind === 'shadow' ? '影子 · 最新' : '模拟实走 · 最新' - return { v: last.ret, label } - } - const rr = (i as Instance & { run_returns?: Record }).run_returns || {} - if (rr.backtest != null) return { v: rr.backtest, label: '回测 · 最新' } - if (rr.replay != null) return { v: rr.replay, label: '回放 · 最新' } - return { v: null, label: '未运行' } -} -function latestRet(i: Instance): number | null { - return retInfoOf(i).v -} const paramSummary = (p: Record): string => Object.entries(p).map(([k, v]) => `${k}=${v}`).join(' · ') @@ -327,8 +310,7 @@ const runningRows = computed(() => #{{ i.id }} · {{ i.interval }} · 更新 {{ i.updated_at }}
{{ paramSummary(i.params) }}
- -
{{ pct(latestRet(i)) }}{{ retInfoOf(i).label }}
+
diff --git a/sanguo_api/routes_live.py b/sanguo_api/routes_live.py index 1567d58..5042cfb 100644 --- a/sanguo_api/routes_live.py +++ b/sanguo_api/routes_live.py @@ -138,7 +138,7 @@ def create_live(req: LiveCreateRequest): # 组合实盘:vt_symbol 占位为池名;setting 存组合参数(supervisor 转发 env) if not payload.get("strategy_class"): raise HTTPException(400, "组合实盘需选择策略(strategy_class)") - payload.setdefault("pool", "hs300_subset") + payload.setdefault("pool", "all") payload.setdefault("max_pool", 30) payload.setdefault("benchmark", "000300.XSHG") payload["vt_symbol"] = payload["pool"] diff --git a/sanguo_api/routes_paper.py b/sanguo_api/routes_paper.py index ab2393a..cca218e 100644 --- a/sanguo_api/routes_paper.py +++ b/sanguo_api/routes_paper.py @@ -61,7 +61,7 @@ class PaperCreateRequest(BaseModel): strategy_type: str = "cta" # 撮合引擎(影子柜台 P1):eod_replay=日终回放(NAS 20:30) / shadow=影子柜台(VPS 盘中实时) engine: str = "eod_replay" - pool: str = "hs300_subset" + pool: str = "all" max_pool: int = 30 benchmark: str = "000300.XSHG" # §12.6 实例做实:账户绑档案;空=发起即建档(自动创建实例再发起) diff --git a/sanguo_api/routes_portfolio.py b/sanguo_api/routes_portfolio.py index cbd2765..3c43a1a 100644 --- a/sanguo_api/routes_portfolio.py +++ b/sanguo_api/routes_portfolio.py @@ -29,7 +29,7 @@ async def verify_token(authorization: str | None = Header(None)): class PortfolioBacktestRequest(BaseModel): - pool: str = Field(default="hs300_subset", description="标的池(占位,MVP 用默认)") + pool: str = Field(default="all", description="标的池(all=全市场)") start_date: str = Field(default="2024-01-01", description="YYYY-MM-DD") end_date: str = Field(default="2024-02-29", description="YYYY-MM-DD") initial_cash: float = Field(default=1_000_000.0, description="初始资金(元)") diff --git a/sanguo_orchestrator/runner.py b/sanguo_orchestrator/runner.py index 49f5d14..a9a7351 100644 --- a/sanguo_orchestrator/runner.py +++ b/sanguo_orchestrator/runner.py @@ -140,7 +140,7 @@ class Orchestrator: async def submit_portfolio(self, start: str, end: str, cash: float, benchmark: str, strategy: str = "all_weather", max_pool: int = 30, - pool: str = "hs300_subset", + pool: str = "all", provider_config=None, commission_rate: float = 0.0003, stamp_duty_rate: float = 0.001,