feat(web): K线周期下拉统一共享常量:列全miniQMT成品K线(1m~60m/日/周/月,周月由日线合成),默认日线;模拟盘/实盘/策略实例三表单同源;标注回放数据可用性(d/15m本地有) [nas]
CI/CD / test (push) Successful in 14s
CI/CD / nas-deploy (push) Successful in 32s
CI/CD / nas-verify (push) Successful in 14s

This commit is contained in:
2026-08-14 09:18:27 +08:00
parent fff9119f38
commit 94e1b59ec1
4 changed files with 57 additions and 18 deletions
+28
View File
@@ -0,0 +1,28 @@
// K 线周期选项(全局共享)。
// 来源=miniQMT(xtdata)成品K线:订阅时直接指明周期,QMT 端已合成好,无需自己从 tick 拼。
// 默认日线;策略/实例可自行改。
// replayOk=本地库有该周期历史数据(回放/回测可用,当前 d/15m);
// 其余周期影子柜台实时订阅可用,回放待本地补数据。
export interface IntervalOption {
label: string
value: string
matchHint?: string
replayOk?: boolean
}
export const DEFAULT_INTERVAL = 'd'
export const INTERVAL_OPTIONS: IntervalOption[] = [
{ label: '日线', value: 'd', matchHint: '每日 1 个撮合点', replayOk: true },
{ label: '60 分钟', value: '60m' },
{ label: '30 分钟', value: '30m' },
{ label: '15 分钟', value: '15m', matchHint: '每日约 16 个撮合点(每根 15m K线撮合)', replayOk: true },
{ label: '5 分钟', value: '5m' },
{ label: '1 分钟', value: '1m' },
{ label: '周线', value: 'w', matchHint: '由日线合成' },
{ label: '月线', value: 'mon', matchHint: '由日线合成' },
]
export function intervalLabel(v: string): string {
return INTERVAL_OPTIONS.find((o) => o.value === v)?.label ?? v
}
+9 -3
View File
@@ -4,6 +4,7 @@ import { useRoute, useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { createLive, type LiveCreateRequest } from '@/api/live'
import { apiClient } from '@/api/client'
import { INTERVAL_OPTIONS } from '@/constants/intervals'
const route = useRoute()
const router = useRouter()
@@ -58,7 +59,7 @@ const form = ref<LiveCreateRequest>({
size: 100,
forbid_short: true,
},
interval: '15m',
interval: 'd',
initial_capital: 1_000_000,
connect_wait_sec: 10,
init_wait_sec: 60,
@@ -206,8 +207,13 @@ async function onSubmit(): Promise<void> {
<span class="muted form-hint">可只写 6 位代码,自动补交易所后缀(.SSE / .SZSE)</span>
</el-form-item>
<el-form-item label="K 线周期">
<el-input v-model="form.interval" placeholder="15m" style="width: 160px" />
<span class="muted form-hint"> 15m / 1m / d</span>
<el-select v-model="form.interval" style="width: 220px">
<el-option v-for="o in INTERVAL_OPTIONS" :key="o.value" :value="o.value" :label="o.label">
<span>{{ o.label }}</span>
<span v-if="!o.replayOk" class="muted" style="float: right; font-size: 12px">影子柜台实时可用·回放暂无本地数据</span>
</el-option>
</el-select>
<span class="muted form-hint">miniQMT 成品K线,默认日线</span>
</el-form-item>
</el-form>
</el-card>
+12 -8
View File
@@ -5,6 +5,7 @@ import { ElMessage } from 'element-plus'
import { createPaper, type PaperCreate } from '@/api/paper'
import { apiClient } from '@/api/client'
import { getStrategies, type StrategyItem } from '@/api/strategy'
import { INTERVAL_OPTIONS } from '@/constants/intervals'
const route = useRoute()
const router = useRouter()
@@ -85,10 +86,6 @@ const modes = [
{ value: 'replay', label: '回放', desc: '历史重放,立即出结果' },
{ value: 'live', label: '实走', desc: '每日定时 step,长期跟踪' },
]
const intervals = [
{ value: 'd', label: '日频' },
{ value: '15m', label: '15 分钟' },
]
const sessions = [
{ value: 'next_open', label: '次日开盘', desc: '收盘型信号,T+1 开盘撮合' },
{ value: 'current_close', label: '当日收盘', desc: '尾盘型信号,当日收盘撮合' },
@@ -209,10 +206,17 @@ function onSymbols(v: string): void {
</div>
</div>
<div v-if="!isPortfolio" class="seg-label" style="margin-top:16px">频率</div>
<el-radio-group v-if="!isPortfolio" v-model="form.interval">
<el-radio-button v-for="i in intervals" :key="i.value" :value="i.value">{{ i.label }}</el-radio-button>
</el-radio-group>
<div v-if="!isPortfolio" class="seg-label" style="margin-top:16px">K 线周期</div>
<el-select v-if="!isPortfolio" v-model="form.interval" style="width: 220px">
<el-option v-for="i in INTERVAL_OPTIONS" :key="i.value"
:value="i.value" :label="i.label">
<span>{{ i.label }}</span>
<span v-if="!i.replayOk" class="muted" style="float:right;font-size:12px">影子柜台实时可用·回放暂无本地数据</span>
</el-option>
</el-select>
<div v-if="!isPortfolio" class="muted" style="font-size:12px;margin-top:4px">
{{ INTERVAL_OPTIONS.find(o => o.value === form.interval)?.matchHint || (form.interval === 'd' ? '' : '分钟级周期需标的分钟数据') }}
</div>
</el-card>
<el-card class="blk" shadow="never">
+8 -7
View File
@@ -3,6 +3,7 @@ import { ref, reactive, computed, onMounted, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import { getParams } from '@/api/strategy'
import { INTERVAL_OPTIONS } from '@/constants/intervals'
import { apiClient } from '@/api/client'
interface CodeFile {
@@ -49,10 +50,7 @@ const POOL_OPTIONS = [
{ label: '中证2000', value: 'ZZ2000' },
{ label: '全市场', value: 'ALL' },
]
const INTERVAL_OPTIONS = [
{ label: '日线', value: 'd', matchHint: '每日 1 个撮合点' },
{ label: '15 分钟', value: '15m', matchHint: '每日约 16 个撮合点(每根 15m K线撮合)' },
]
// miniQMT 成品K线周期全量选项(共享常量,默认日线)
const SESSION_OPTIONS = [
{ label: '下一根 K线 · 开盘价(防未来函数,推荐)', value: 'next_open', dayOnly: false },
{ label: '当前 K线 · 收盘价', value: 'current_close', dayOnly: false },
@@ -220,9 +218,12 @@ async function onDelete(): Promise<void> {
<template #header><span class="section-title">撮合 · 精度与时点</span></template>
<el-form label-width="100px">
<el-form-item label="数据精度">
<el-radio-group v-model="form.interval">
<el-radio-button v-for="o in INTERVAL_OPTIONS" :key="o.value" :value="o.value">{{ o.label }}</el-radio-button>
</el-radio-group>
<el-select v-model="form.interval" style="width: 220px">
<el-option v-for="o in INTERVAL_OPTIONS" :key="o.value" :value="o.value" :label="o.label">
<span>{{ o.label }}</span>
<span v-if="!o.replayOk" class="muted" style="float: right; font-size: 12px">影子柜台实时可用·回放暂无本地数据</span>
</el-option>
</el-select>
<span class="muted form-hint">撮合频率 = 精度 · {{ matchHint }}</span>
</el-form-item>
<el-form-item label="撮合时点">