From 4da714d1c59169b5fbfb9e300c088bc392c87cb6 Mon Sep 17 00:00:00 2001 From: claude_dev Date: Sat, 15 Aug 2026 23:04:28 +0800 Subject: [PATCH] =?UTF-8?q?feat(frontend):=20=E5=9B=9E=E6=B5=8B=E4=B8=A4?= =?UTF-8?q?=E9=A1=B5=E8=A1=A5=E5=AE=9E=E4=BE=8B=E6=A1=A3=E6=A1=88=E4=B8=8B?= =?UTF-8?q?=E6=8B=89=E2=80=94=E2=80=94=E5=9B=9E=E6=B5=8B/=E6=A8=A1?= =?UTF-8?q?=E6=8B=9F/=E5=AE=9E=E7=9B=98=E4=B8=89=E5=85=A5=E5=8F=A3?= =?UTF-8?q?=E9=83=BD=E8=83=BD=E9=80=89=E6=A1=A3=E6=A1=88(=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E9=AA=8C=E6=94=B6=E8=BF=BD=E9=97=AE);CTA=E9=A1=B5inst?= =?UTF-8?q?ance=E9=A2=84=E5=A1=AB=E6=8F=90=E5=8F=96applyInstance=E5=A4=8D?= =?UTF-8?q?=E7=94=A8(=E8=B7=B3=E8=BD=AC=E5=B8=A6=E5=85=A5+=E4=B8=8B?= =?UTF-8?q?=E6=8B=89=E9=80=89=E6=8B=A9=E7=BB=9F=E4=B8=80selectedInstanceId?= =?UTF-8?q?);=E7=BB=84=E5=90=88=E9=A1=B5=E5=8A=A0=E4=B8=8B=E6=8B=89+?= =?UTF-8?q?=E9=A2=84=E5=A1=AB(=E7=AD=96=E7=95=A5/max=5Fpool/benchmark/pool?= =?UTF-8?q?)+getInstances=E5=8A=A0=E8=BD=BD;=E6=8F=90=E4=BA=A4instance=5Fi?= =?UTF-8?q?d=E6=94=B9=E7=94=A8selectedInstanceId;build=E7=BB=BF=20[vps]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/views/backtest/New.vue | 69 ++++++++++++++----- .../src/views/backtest/PortfolioBacktest.vue | 39 ++++++++++- 2 files changed, 87 insertions(+), 21 deletions(-) diff --git a/frontend/src/views/backtest/New.vue b/frontend/src/views/backtest/New.vue index 5cdee21..669b170 100644 --- a/frontend/src/views/backtest/New.vue +++ b/frontend/src/views/backtest/New.vue @@ -2,7 +2,7 @@ import { ref, reactive, watch, onMounted } from 'vue' import { useRoute, useRouter } from 'vue-router' import { ElMessage } from 'element-plus' -import { getStrategies, getParams, type StrategyItem } from '@/api/strategy' +import { getStrategies, getParams, getInstances, type StrategyItem } from '@/api/strategy' import { submitCta, getTaskParams } from '@/api/backtest' import { disableFutureDate } from '@/utils/dates' import { apiClient } from '@/api/client' @@ -85,27 +85,46 @@ async function loadParams(cls: string, override: Record | null) } } +// §12.6 选档案:跳转带入或页面下拉选择,提交带 instance_id 回写四格 +const instanceOptions = ref<{ id: number; name: string; code_file: string; interval: string }[]>([]) +const selectedInstanceId = ref(null) + +async function applyInstance(instId: string | number): Promise { + try { + const [{ data: ir }, { data: fr }] = await Promise.all([ + apiClient.get<{ instance: InstLike & { name?: string } }>(`/strategy/instances/${instId}`), + apiClient.get<{ files: FileLike[] }>('/strategy/files'), + ]) + const inst = ir.instance + fromInstance.value = inst.name || String(instId) + const file = fr.files.find((f) => f.name === inst.code_file) + form.strategy = file?.class_name || inst.code_file + form.symbol = inst.symbol_or_pool || form.symbol + form.interval = inst.interval || form.interval + await loadParams(form.strategy, inst.params) + } catch { + /* 预填失败,走默认 */ + } +} + +function onPickInstance(id: number | null): void { + selectedInstanceId.value = id + if (id != null) void applyInstance(id) +} + onMounted(async () => { loading.value = true try { strategies.value = await getStrategies() + try { + instanceOptions.value = await getInstances() + } catch { + /* 档案下拉失败不阻塞 */ + } const instId = route.query.instance - if (instId) { - try { - const [{ data: ir }, { data: fr }] = await Promise.all([ - apiClient.get<{ instance: InstLike & { name?: string } }>(`/strategy/instances/${instId}`), - apiClient.get<{ files: FileLike[] }>('/strategy/files'), - ]) - const inst = ir.instance - fromInstance.value = inst.name || String(instId) - const file = fr.files.find((f) => f.name === inst.code_file) - form.strategy = file?.class_name || inst.code_file - form.symbol = inst.symbol_or_pool || form.symbol - form.interval = inst.interval || form.interval - await loadParams(form.strategy, inst.params) - } catch { - /* 预填失败,走默认 */ - } + if (instId && !Array.isArray(instId)) { + selectedInstanceId.value = Number(instId) + await applyInstance(instId) } // 从代码编辑页「运行回测」跳转:按策略类名预填 if (!form.strategy && route.query.class) { @@ -167,7 +186,7 @@ async function onSubmit(): Promise { commission_rate: Number(form.commission_rate), stamp_duty_rate: Number(form.stamp_duty_rate), slippage: Number(form.slippage), - instance_id: route.query.instance && !Array.isArray(route.query.instance) ? Number(route.query.instance) : null, + instance_id: selectedInstanceId.value, }) ElMessage.success('回测已提交') void tid @@ -192,6 +211,20 @@ async function onSubmit(): Promise { + + + + + 已绑档案 #{{ selectedInstanceId }},完成后回写档案 + diff --git a/frontend/src/views/backtest/PortfolioBacktest.vue b/frontend/src/views/backtest/PortfolioBacktest.vue index 0bf39cb..57861f2 100644 --- a/frontend/src/views/backtest/PortfolioBacktest.vue +++ b/frontend/src/views/backtest/PortfolioBacktest.vue @@ -6,6 +6,7 @@ import type { EChartsCoreOption } from 'echarts' import { useChart } from '@/composables/useChart' import { darkTitle, darkTooltip, darkGrid, darkAxis } from '@/utils/echartsDark' import { getTaskParams } from '@/api/backtest' +import { getInstances } from '@/api/strategy' import { apiClient } from '@/api/client' import { disableFutureDate } from '@/utils/dates' import { INTERVAL_OPTIONS } from '@/constants/intervals' @@ -51,6 +52,8 @@ function loadDateRange(): { start: string; end: string } { return { start: f(start), end: f(end) } } const dr = loadDateRange() +// §12.6 选档案(跳转带入或页面下拉),提交带 instance_id 回写四格 +const selectedInstanceId = ref(null) const form = reactive({ pool: 'hs300_subset', strategy: 'all_weather', @@ -371,7 +374,7 @@ async function onSubmit(): Promise { min_commission: Number(form.min_commission), slippage: Number(form.slippage), interval: form.interval, - instance_id: route.query.instance && !Array.isArray(route.query.instance) ? Number(route.query.instance) : null, + instance_id: selectedInstanceId.value, }) ElMessage.success('回测已提交,后台运行中') router.push('/backtest/history') // 跳任务中心(历史任务页)看进度/结果 @@ -383,7 +386,7 @@ async function onSubmit(): Promise { const fromTaskId = computed(() => (route.query.from_task as string) || '') -onMounted(() => { +onMounted(async () => { if (viewTaskId.value) { loadResult(viewTaskId.value) } else if (route.query.strategy) { @@ -395,9 +398,25 @@ onMounted(() => { } // §12.6 从策略库档案发起:按档案参数预填(max_pool/benchmark 等) const instQ = route.query.instance - if (instQ && !Array.isArray(instQ)) void prefillFromInstance(Number(instQ)) + if (instQ && !Array.isArray(instQ)) { + selectedInstanceId.value = Number(instQ) + void prefillFromInstance(Number(instQ)) + } + try { + instanceOptions.value = await getInstances() + } catch { + /* 档案下拉失败不阻塞 */ + } }) +// §12.6 选档案:跳转带入或页面下拉选择,提交带 instance_id 回写四格 +const instanceOptions = ref<{ id: number; name: string; code_file: string; interval: string }[]>([]) + +function onPickInstance(id: number | null): void { + selectedInstanceId.value = id + if (id != null) void prefillFromInstance(id) +} + async function prefillFromInstance(instId: number): Promise { try { const { data } = await apiClient.get<{ @@ -461,6 +480,20 @@ function fmtNum(v: number | null | undefined, digits = 2): string { + + + + + 已绑档案 #{{ selectedInstanceId }},完成后回写档案 +