From c41fd862ea4c2b6e4bf6d523416a0f7e8a6fe4fc Mon Sep 17 00:00:00 2001 From: claude_dev Date: Thu, 13 Aug 2026 18:17:16 +0800 Subject: [PATCH] =?UTF-8?q?feat(frontend):=20=E9=AA=8C=E6=94=B6UX=E6=89=B9?= =?UTF-8?q?A:=20CTA=E5=9B=9E=E6=B5=8B=E6=8F=90=E4=BA=A4=E7=BB=9F=E4=B8=80?= =?UTF-8?q?=E8=B7=B3=E4=BB=BB=E5=8A=A1=E4=B8=AD=E5=BF=83;=20=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E7=BC=96=E8=BE=91=E3=80=8C=E8=BF=90=E8=A1=8C=E5=9B=9E?= =?UTF-8?q?=E6=B5=8B=E3=80=8D=E7=9B=B4=E6=8E=A5=E5=8F=91=E8=B5=B7(?= =?UTF-8?q?=E9=BB=98=E8=AE=A4=E5=8F=82=E6=95=B0+=E6=9C=80=E8=BF=911?= =?UTF-8?q?=E5=B9=B4,CTA/=E7=BB=84=E5=90=88=E5=88=86=E6=B5=81);=20?= =?UTF-8?q?=E6=A8=A1=E6=8B=9F=E7=9B=98=E7=AD=96=E7=95=A5=E6=94=B9=E4=B8=8B?= =?UTF-8?q?=E6=8B=89;=20=E7=8A=B6=E6=80=81=E5=BE=BD=E6=A0=87=E5=AE=9E?= =?UTF-8?q?=E8=B5=B0=E6=96=87=E6=A1=88+=E5=8E=BB=E6=8E=92=E9=98=9F?= =?UTF-8?q?=E5=85=9C=E5=BA=95=20[nas]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/views/backtest/New.vue | 5 +- frontend/src/views/paper/List.vue | 2 +- frontend/src/views/paper/New.vue | 24 +++++++-- frontend/src/views/paper/Result.vue | 8 ++- frontend/src/views/strategy/Code.vue | 74 ++++++++++++++++++++++++++-- 5 files changed, 101 insertions(+), 12 deletions(-) diff --git a/frontend/src/views/backtest/New.vue b/frontend/src/views/backtest/New.vue index 3b60079..0898317 100644 --- a/frontend/src/views/backtest/New.vue +++ b/frontend/src/views/backtest/New.vue @@ -151,7 +151,8 @@ async function onSubmit(): Promise { slippage: Number(form.slippage), }) ElMessage.success('回测已提交') - router.push(`/backtest/progress/${tid}`) + void tid + router.push('/backtest/history') } catch { ElMessage.error('提交失败') } finally { @@ -165,7 +166,7 @@ async function onSubmit(): Promise {

个股回测

-

CTA 策略历史回测 · 选择策略与标的,提交后进入进度跟踪

+

CTA 策略历史回测 · 提交后可在「历史任务」跟踪进度与结果

diff --git a/frontend/src/views/paper/List.vue b/frontend/src/views/paper/List.vue index 55215c4..c227961 100644 --- a/frontend/src/views/paper/List.vue +++ b/frontend/src/views/paper/List.vue @@ -41,7 +41,7 @@ const stats = computed(() => ({ })) const modeLabel: Record = { replay: '回放', live: '实走' } -const statusLabel: Record = { running: '运行中', done: '完成', failed: '失败', created: '已创建', pending: '排队' } +const statusLabel: Record = { running: '运行中', done: '完成', failed: '失败', created: '已创建', pending: '排队', stopped: '已停止' } function pct(v: number | null | undefined): string { if (v == null || !Number.isFinite(v)) return '—' diff --git a/frontend/src/views/paper/New.vue b/frontend/src/views/paper/New.vue index 82a123a..bb72255 100644 --- a/frontend/src/views/paper/New.vue +++ b/frontend/src/views/paper/New.vue @@ -4,11 +4,21 @@ import { useRoute, useRouter } from 'vue-router' import { ElMessage } from 'element-plus' import { createPaper, type PaperCreate } from '@/api/paper' import { apiClient } from '@/api/client' +import { getStrategies, type StrategyItem } from '@/api/strategy' const route = useRoute() const router = useRouter() const loading = ref(false) const fromInstance = ref('') +const strategyOptions = ref([]) + +onMounted(async () => { + try { + strategyOptions.value = await getStrategies() + } catch { + /* 下拉加载失败不阻塞表单 */ + } +}) function loadDateRange(): { start: string; end: string } { try { const s = localStorage.getItem('paper_date_range') @@ -137,9 +147,17 @@ function onSymbols(v: string): void { /> 多个标的用英文逗号分隔 - - - CTA 策略类名,如 DoubleMaStrategy + + + + + CTA 策略(个股),如 DoubleMaStrategy diff --git a/frontend/src/views/paper/Result.vue b/frontend/src/views/paper/Result.vue index 9e6c2a3..e021bf4 100644 --- a/frontend/src/views/paper/Result.vue +++ b/frontend/src/views/paper/Result.vue @@ -97,7 +97,7 @@ function num(v: number | undefined | null): string { return Math.round(v).toLocaleString('zh-CN') } function statusLabel(s: string): string { - return ({ running: '运行中', done: '完成', failed: '失败', created: '已创建', pending: '排队' } as Record)[s] || s + return ({ running: '运行中', done: '完成', failed: '失败', created: '已创建', pending: '排队', stopped: '已停止' } as Record)[s] || s } function rowClass({ row }: { row: PaperTrade }): string { return row.rejected ? 'rejected-row' : '' @@ -119,7 +119,11 @@ function toLive(): void {

- {{ statusLabel(account?.status ?? '') }} + {{ + account.mode === 'live' && account.status === 'running' + ? '实走中 · 每日 20:30 结算' + : statusLabel(account.status) + }} 实走监控 →
diff --git a/frontend/src/views/strategy/Code.vue b/frontend/src/views/strategy/Code.vue index 4cf4884..8f951a0 100644 --- a/frontend/src/views/strategy/Code.vue +++ b/frontend/src/views/strategy/Code.vue @@ -4,6 +4,9 @@ import { useRoute, useRouter } from 'vue-router' import { ElMessage } from 'element-plus' import MonacoEditor from '@/components/MonacoEditor.vue' import { apiClient } from '@/api/client' +import { getParams } from '@/api/strategy' +import { submitCta } from '@/api/backtest' +import { postPortfolioBacktest } from '@/api/portfolio' interface StrategyFile { name: string @@ -76,9 +79,72 @@ async function onSave(): Promise { } } -function runBacktest(): void { - if (active.value) { - router.push({ path: '/backtest/new', query: { class: active.value.class_name } }) +const running = ref(false) + +function lastDateRange(): { start: string; end: string } { + try { + const s = localStorage.getItem('bt_date_range') + if (s) return JSON.parse(s) as { start: string; end: string } + } catch { + /* ignore */ + } + const end = new Date() + const start = new Date(end) + start.setFullYear(start.getFullYear() - 1) + const f = (d: Date): string => d.toISOString().slice(0, 10) + return { start: f(start), end: f(end) } +} + +// 直接用当前策略发起回测:默认参数 + 最近区间,提交后跳「历史任务」看进度 +async function runBacktest(): Promise { + if (!active.value || running.value) return + if (dirty.value) { + ElMessage.warning('有未保存修改,请先保存再运行回测') + return + } + running.value = true + try { + const dr = lastDateRange() + if (active.value.type === 'portfolio') { + await postPortfolioBacktest({ + pool: 'hs300_subset', + strategy: active.value.name.replace(/\.py$/, ''), + max_pool: 30, + start_date: dr.start, + end_date: dr.end, + initial_cash: 1_000_000, + benchmark: '000300.XSHG', + commission_rate: 0.0003, + stamp_duty_rate: 0.001, + min_commission: 5, + slippage: 0.001, + }) + } else { + const p = await getParams(active.value.class_name) + const params: Record = {} + p.parameters.forEach((k) => { + const v = p.defaults[k] + params[k] = v != null ? v : '' + }) + await submitCta({ + symbol: '600000', + strategy: active.value.class_name, + params, + start: dr.start, + end: dr.end, + benchmark: 'hs300', + interval: 'd', + commission_rate: 0.0003, + stamp_duty_rate: 0.001, + slippage: 0.001, + }) + } + ElMessage.success('回测已提交,可在「历史任务」查看进度') + router.push('/backtest/history') + } catch { + ElMessage.error('回测提交失败') + } finally { + running.value = false } } @@ -125,7 +191,7 @@ function runBacktest(): void { {{ active?.modified }} - +