新增自定义时间段批量打分功能:支持设置日期区间,对区间内每天自动拉取数据并打分
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -3,9 +3,11 @@ import { nextTick, onMounted, reactive, ref, watch } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import {
|
||||
runPipeline,
|
||||
runRange,
|
||||
getActiveContract,
|
||||
type ActiveContract,
|
||||
type RunResponse,
|
||||
type RunRangeResponse,
|
||||
} from '@/api/run'
|
||||
import { parseTsCode } from '@/utils/contract'
|
||||
import { useMobile } from '@/composables/useMobile'
|
||||
@@ -14,6 +16,8 @@ const { isMobile } = useMobile()
|
||||
|
||||
const SYMBOLS = ['FG', 'SA', 'RB', 'MA', 'CF', 'M']
|
||||
|
||||
const mode = ref<'single' | 'range'>('single')
|
||||
|
||||
const form = reactive<{
|
||||
symbol: string
|
||||
trade_date: string
|
||||
@@ -22,20 +26,32 @@ const form = reactive<{
|
||||
trade_date: '',
|
||||
})
|
||||
|
||||
const range = reactive<{
|
||||
dates: [string, string] | []
|
||||
}>({
|
||||
dates: [],
|
||||
})
|
||||
|
||||
const active = ref<ActiveContract | null>(null)
|
||||
const activeLoading = ref(false)
|
||||
const loading = ref(false)
|
||||
const result = ref<RunResponse | null>(null)
|
||||
const rangeResult = ref<RunRangeResponse | null>(null)
|
||||
const resultRef = ref<HTMLElement | null>(null)
|
||||
|
||||
async function loadActive() {
|
||||
activeLoading.value = true
|
||||
try {
|
||||
active.value = await getActiveContract(form.symbol)
|
||||
// 切换品种后,如果原日期落在新合约的可选范围之外,清空它
|
||||
if (form.trade_date && !isDateAllowed(toDate(form.trade_date))) {
|
||||
form.trade_date = ''
|
||||
}
|
||||
if (Array.isArray(range.dates) && range.dates.length === 2) {
|
||||
const [s, e] = range.dates
|
||||
if (!isDateAllowed(toDate(s)) || !isDateAllowed(toDate(e))) {
|
||||
range.dates = []
|
||||
}
|
||||
}
|
||||
} catch (err: any) {
|
||||
active.value = null
|
||||
ElMessage.error(err?.response?.data?.error || '加载主力合约失败')
|
||||
@@ -45,7 +61,6 @@ async function loadActive() {
|
||||
}
|
||||
|
||||
function toDate(s: string) {
|
||||
// s 形如 'YYYY-MM-DD'
|
||||
const [y, m, d] = s.split('-').map(Number)
|
||||
return new Date(y, m - 1, d)
|
||||
}
|
||||
@@ -69,12 +84,29 @@ async function submit() {
|
||||
}
|
||||
loading.value = true
|
||||
result.value = null
|
||||
rangeResult.value = null
|
||||
try {
|
||||
const req: { symbol: string; trade_date?: string } = { symbol: form.symbol }
|
||||
if (form.trade_date) req.trade_date = form.trade_date.replace(/-/g, '')
|
||||
const resp = await runPipeline(req)
|
||||
result.value = resp
|
||||
ElMessage.success('打分完成')
|
||||
if (mode.value === 'single') {
|
||||
const req: { symbol: string; trade_date?: string } = { symbol: form.symbol }
|
||||
if (form.trade_date) req.trade_date = form.trade_date.replace(/-/g, '')
|
||||
const resp = await runPipeline(req)
|
||||
result.value = resp
|
||||
ElMessage.success('打分完成')
|
||||
} else {
|
||||
if (!Array.isArray(range.dates) || range.dates.length !== 2) {
|
||||
ElMessage.warning('请选择日期区间')
|
||||
loading.value = false
|
||||
return
|
||||
}
|
||||
const [start, end] = range.dates
|
||||
const resp = await runRange({
|
||||
symbol: form.symbol,
|
||||
start_date: start.replace(/-/g, ''),
|
||||
end_date: end.replace(/-/g, ''),
|
||||
})
|
||||
rangeResult.value = resp
|
||||
ElMessage.success(`区间打分完成,成功 ${resp.scored} 条`)
|
||||
}
|
||||
await nextTick()
|
||||
resultRef.value?.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
||||
} catch (err: any) {
|
||||
@@ -104,6 +136,12 @@ onMounted(loadActive)
|
||||
<span>手动打分</span>
|
||||
</template>
|
||||
<el-form :model="form" label-width="100px" style="max-width: 480px">
|
||||
<el-form-item label="模式">
|
||||
<el-radio-group v-model="mode">
|
||||
<el-radio-button label="single">单日打分</el-radio-button>
|
||||
<el-radio-button label="range">区间打分</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="品种">
|
||||
<el-select v-model="form.symbol" :loading="activeLoading" style="width: 100%">
|
||||
<el-option v-for="s in SYMBOLS" :key="s" :label="s" :value="s" />
|
||||
@@ -118,7 +156,7 @@ onMounted(loadActive)
|
||||
</span>
|
||||
<el-text v-else type="info">加载中…</el-text>
|
||||
</el-form-item>
|
||||
<el-form-item label="打分日期">
|
||||
<el-form-item v-if="mode === 'single'" label="打分日期">
|
||||
<el-date-picker
|
||||
v-model="form.trade_date"
|
||||
type="date"
|
||||
@@ -129,9 +167,23 @@ onMounted(loadActive)
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item v-else label="日期区间">
|
||||
<el-date-picker
|
||||
v-model="range.dates"
|
||||
type="daterange"
|
||||
:placeholder="active ? `${active.min_date} ~ ${active.max_date}` : '加载中…'"
|
||||
value-format="YYYY-MM-DD"
|
||||
:disabled-date="disabledDate"
|
||||
:disabled="!active"
|
||||
range-separator="→"
|
||||
start-placeholder="开始"
|
||||
end-placeholder="结束"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="loading" :disabled="!active" @click="submit">
|
||||
执行打分
|
||||
{{ mode === 'single' ? '执行打分' : '批量打分' }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
@@ -160,6 +212,41 @@ onMounted(loadActive)
|
||||
</el-descriptions>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<div v-if="rangeResult" ref="resultRef">
|
||||
<el-card shadow="never" class="result-card">
|
||||
<template #header>
|
||||
<span>区间打分结果</span>
|
||||
</template>
|
||||
<el-descriptions :column="isMobile ? 1 : 2" border>
|
||||
<el-descriptions-item label="合约">{{ parseTsCode(rangeResult.ts_code).symbol }}</el-descriptions-item>
|
||||
<el-descriptions-item label="区间">{{ rangeResult.start_date }} ~ {{ rangeResult.end_date }}</el-descriptions-item>
|
||||
<el-descriptions-item label="成功">{{ rangeResult.scored }} 条</el-descriptions-item>
|
||||
<el-descriptions-item label="跳过">{{ rangeResult.skipped }} 条</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<el-alert
|
||||
v-if="rangeResult.warnings.length > 0"
|
||||
:title="`警告 (${rangeResult.warnings.length} 条)`"
|
||||
type="warning"
|
||||
:closable="false"
|
||||
style="margin-top: 12px"
|
||||
>
|
||||
<div style="max-height: 120px; overflow-y: auto">
|
||||
<div v-for="(w, i) in rangeResult.warnings" :key="i" style="font-size: 12px">{{ w }}</div>
|
||||
</div>
|
||||
</el-alert>
|
||||
<el-table :data="rangeResult.results" stripe style="margin-top: 16px" max-height="400">
|
||||
<el-table-column prop="trade_date" label="日期" width="110" />
|
||||
<el-table-column prop="close" label="收盘" width="90" />
|
||||
<el-table-column prop="composite" label="综合" width="80" />
|
||||
<el-table-column label="信号">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="signalTagType(row.signal)" size="small">{{ row.signal }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -169,4 +256,7 @@ onMounted(loadActive)
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
.result-card {
|
||||
margin-top: 8px;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user