feat(frontend): 回测两页补实例档案下拉——回测/模拟/实盘三入口都能选档案(用户验收追问);CTA页instance预填提取applyInstance复用(跳转带入+下拉选择统一selectedInstanceId);组合页加下拉+预填(策略/max_pool/benchmark/pool)+getInstances加载;提交instance_id改用selectedInstanceId;build绿 [vps]
This commit is contained in:
@@ -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<string, unknown> | null)
|
||||
}
|
||||
}
|
||||
|
||||
// §12.6 选档案:跳转带入或页面下拉选择,提交带 instance_id 回写四格
|
||||
const instanceOptions = ref<{ id: number; name: string; code_file: string; interval: string }[]>([])
|
||||
const selectedInstanceId = ref<number | null>(null)
|
||||
|
||||
async function applyInstance(instId: string | number): Promise<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
<el-card class="blk" shadow="never" v-loading="loading">
|
||||
<template #header><span class="section-title">策略与参数</span></template>
|
||||
<el-form :model="form" label-width="120px">
|
||||
<el-form-item label="实例档案">
|
||||
<el-select
|
||||
:model-value="selectedInstanceId" clearable filterable
|
||||
placeholder="选档案带出参数并回写结果(可选);不选=自由回测"
|
||||
style="width: 420px"
|
||||
@update:model-value="onPickInstance"
|
||||
>
|
||||
<el-option
|
||||
v-for="i in instanceOptions" :key="i.id" :value="i.id"
|
||||
:label="`${i.name}(${i.code_file || '—'}·${i.interval})`"
|
||||
/>
|
||||
</el-select>
|
||||
<span v-if="selectedInstanceId != null" class="muted form-hint">已绑档案 #{{ selectedInstanceId }},完成后回写档案</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="策略">
|
||||
<el-select v-model="form.strategy" placeholder="选择策略" style="width: 320px">
|
||||
<el-option v-for="s in strategies" :key="s.name" :label="s.name" :value="s.name" />
|
||||
|
||||
@@ -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<number | null>(null)
|
||||
const form = reactive({
|
||||
pool: 'hs300_subset',
|
||||
strategy: 'all_weather',
|
||||
@@ -371,7 +374,7 @@ async function onSubmit(): Promise<void> {
|
||||
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<void> {
|
||||
|
||||
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<void> {
|
||||
try {
|
||||
const { data } = await apiClient.get<{
|
||||
@@ -461,6 +480,20 @@ function fmtNum(v: number | null | undefined, digits = 2): string {
|
||||
<el-card class="blk" shadow="never">
|
||||
<template #header><span class="section-title">策略与选股</span></template>
|
||||
<el-form :model="form" label-width="120px">
|
||||
<el-form-item label="实例档案">
|
||||
<el-select
|
||||
:model-value="selectedInstanceId" clearable filterable
|
||||
placeholder="选档案带出参数并回写结果(可选);不选=自由回测"
|
||||
style="width: 420px"
|
||||
@update:model-value="onPickInstance"
|
||||
>
|
||||
<el-option
|
||||
v-for="i in instanceOptions" :key="i.id" :value="i.id"
|
||||
:label="`${i.name}(${i.code_file || '—'}·${i.interval})`"
|
||||
/>
|
||||
</el-select>
|
||||
<span v-if="selectedInstanceId != null" class="muted form-hint">已绑档案 #{{ selectedInstanceId }},完成后回写档案</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="标的池">
|
||||
<el-select v-model="form.pool" style="width: 320px">
|
||||
<el-option v-for="opt in poolOptions" :key="opt.value" :label="opt.label" :value="opt.value" />
|
||||
|
||||
Reference in New Issue
Block a user