mirror of
https://ghfast.top/https://github.com/aeroxw/easy_tdx_max.git
synced 2026-09-12 21:34:21 +08:00
feat(web-ui): 取行情整合 + 市场智能识别 + 一键寻优全策略
- 取消单标的/寻优页独立的「取行情」按钮,整合进「开始回测/开始寻测」 (SymbolPicker 经 defineExpose 暴露 loadBars,父组件串联取数+回测) - 取消市场手动选择(沪/深/北交所下拉),改为 market.ts 按代码段智能 识别(17 边界用例验证),代码框旁显示识别结果 - 成交价下拉精简为中文「开盘价/收盘价」,初始资金默认 100 万 - 寻优页 ParamGridPicker 切换策略自动填入预设参数网格 - 寻优页新增「一键寻优所有策略」按钮 + 全局策略排名表 (OptimizeView 复用 store.runOptimizeAll,结果区含最佳/排名/合计网格点) - types/api/store 新增 OptimizeAll 契约 + submitOptimizeAllTask - vue-tsc + vite build 通过
This commit is contained in:
@@ -50,12 +50,28 @@ function onInput(name: string, val: string) {
|
||||
syncOutputs()
|
||||
}
|
||||
|
||||
// 切换策略时清空选择
|
||||
// 把预设取值列表格式化为输入框文本(逗号分隔)
|
||||
function presetToText(vals: Array<number | string>): string {
|
||||
return vals.join(', ')
|
||||
}
|
||||
|
||||
// 切换策略时:若有预设网格则自动勾选并填入预设取值,否则清空选择
|
||||
watch(
|
||||
() => props.strategy?.name,
|
||||
() => {
|
||||
selected.value = new Set()
|
||||
inputs.value = {}
|
||||
const preset = props.strategy?.preset_grid
|
||||
if (preset && Object.keys(preset).length > 0) {
|
||||
const names = Object.keys(preset)
|
||||
selected.value = new Set(names)
|
||||
const newInputs: Record<string, string> = {}
|
||||
for (const n of names) {
|
||||
newInputs[n] = presetToText(preset[n])
|
||||
}
|
||||
inputs.value = newInputs
|
||||
} else {
|
||||
selected.value = new Set()
|
||||
inputs.value = {}
|
||||
}
|
||||
syncOutputs()
|
||||
},
|
||||
)
|
||||
@@ -71,7 +87,10 @@ const gridPoints = computed(() => {
|
||||
|
||||
<template>
|
||||
<div class="grid-picker">
|
||||
<p class="hint">勾选 1-2 个参数寻优,填入取值列表(逗号分隔):</p>
|
||||
<p class="hint">
|
||||
勾选 1-2 个参数寻优,填入取值列表(逗号分隔)。
|
||||
切换策略会自动填入预设参数,可直接编辑:
|
||||
</p>
|
||||
<div v-for="p in strategy?.params" :key="p.name" class="param-row">
|
||||
<label class="check">
|
||||
<input
|
||||
|
||||
@@ -1,19 +1,24 @@
|
||||
<script setup lang="ts">
|
||||
// 多标的输入(组合回测用)。逐个添加 市场:代码,可删除。
|
||||
// 多标的输入(组合回测用)。逐个添加 6 位代码,市场自动识别。
|
||||
// 删除手动市场选择(沪市/深市/北交所),由 detectMarket 智能匹配。
|
||||
|
||||
import { ref } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import { detectMarket, marketLabel } from '../market'
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: string[]
|
||||
}>()
|
||||
const emit = defineEmits<{ 'update:modelValue': [value: string[]] }>()
|
||||
|
||||
const market = ref('SZ')
|
||||
const code = ref('')
|
||||
const detectedMarket = computed(() => (code.value && /^\d{6}$/.test(code.value)
|
||||
? marketLabel(detectMarket(code.value))
|
||||
: ''))
|
||||
|
||||
function add() {
|
||||
if (!/^\d{6}$/.test(code.value)) return
|
||||
const sym = `${market.value}:${code.value}`
|
||||
const sym = `${detectMarket(code.value)}:${code.value}`
|
||||
if (!props.modelValue.includes(sym)) {
|
||||
emit('update:modelValue', [...props.modelValue, sym])
|
||||
}
|
||||
@@ -27,20 +32,16 @@ function remove(sym: string) {
|
||||
|
||||
<template>
|
||||
<div class="stocks-picker">
|
||||
<div class="row">
|
||||
<select v-model="market">
|
||||
<option value="SZ">深市</option>
|
||||
<option value="SH">沪市</option>
|
||||
<option value="BJ">北交所</option>
|
||||
</select>
|
||||
<div class="row add-row">
|
||||
<input
|
||||
v-model="code"
|
||||
maxlength="6"
|
||||
placeholder="6位代码"
|
||||
placeholder="6位代码(市场自动识别)"
|
||||
@keyup.enter="add"
|
||||
/>
|
||||
<button @click="add">添加</button>
|
||||
</div>
|
||||
<p v-if="detectedMarket" class="market-hint">将识别为:{{ detectedMarket }}</p>
|
||||
|
||||
<div v-if="modelValue.length" class="stock-list">
|
||||
<span v-for="s in modelValue" :key="s" class="stock-tag">
|
||||
@@ -53,16 +54,18 @@ function remove(sym: string) {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.row {
|
||||
.add-row {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
}
|
||||
.row select {
|
||||
width: auto;
|
||||
}
|
||||
.row input {
|
||||
.add-row input {
|
||||
flex: 1;
|
||||
}
|
||||
.market-hint {
|
||||
color: var(--text-dim);
|
||||
font-size: 11px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
.stock-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
<script setup lang="ts">
|
||||
// 选标的 + 取行情(按日期范围)。
|
||||
// 选标的 + 配置日期范围(取行情由父组件在「开始回测/开始寻优」时触发)。
|
||||
// 市场按 6 位代码智能识别,不再手动选择。
|
||||
// 后端 /bars 仅支持 count(上限 800,约 3.2 年),固定拉满后前端按日期过滤。
|
||||
// 默认:结束日=今天(最近交易日),开始日=3年前。
|
||||
|
||||
import { ref } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import { fetchBars, formatError } from '../api'
|
||||
import { detectMarket, marketLabel } from '../market'
|
||||
import { useBacktestStore } from '../stores/backtest'
|
||||
import type { Category } from '../types'
|
||||
|
||||
const store = useBacktestStore()
|
||||
|
||||
const market = ref('SZ')
|
||||
const code = ref('000001')
|
||||
const category = ref<Category>('DAY')
|
||||
|
||||
@@ -24,27 +25,38 @@ function isoDaysFromNow(days: number): string {
|
||||
const endDate = ref(isoDaysFromNow(0))
|
||||
const startDate = ref(isoDaysFromNow(-365 * 3))
|
||||
|
||||
const loading = ref(false)
|
||||
const error = ref('')
|
||||
// loading 由父组件控制(回测/寻优时驱动),组件自身只暴露 loadBars
|
||||
const loading = ref(false)
|
||||
|
||||
const CATEGORIES: Category[] = ['DAY', 'WEEK', 'MONTH', 'MIN_5', 'MIN_15', 'MIN_30', 'MIN_60']
|
||||
|
||||
async function loadBars() {
|
||||
// 智能识别的市场(用于提示展示)
|
||||
const detectedMarket = computed(() => (code.value && /^\d{6}$/.test(code.value)
|
||||
? marketLabel(detectMarket(code.value))
|
||||
: ''))
|
||||
|
||||
/** 取行情(由父组件在点击「开始回测/开始寻优」时调用)。
|
||||
* 成功返回 true,失败返回 false(并把错误写入 store.error 供父组件感知)。 */
|
||||
async function loadBars(): Promise<boolean> {
|
||||
// 基本校验
|
||||
if (!/^\d{6}$/.test(code.value)) {
|
||||
error.value = '股票代码必须是 6 位数字'
|
||||
return
|
||||
store.error = error.value
|
||||
return false
|
||||
}
|
||||
if (startDate.value >= endDate.value) {
|
||||
error.value = '开始日期必须早于结束日期'
|
||||
return
|
||||
store.error = error.value
|
||||
return false
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
error.value = ''
|
||||
try {
|
||||
const market = detectMarket(code.value)
|
||||
const bars = await fetchBars(
|
||||
market.value,
|
||||
market,
|
||||
code.value,
|
||||
category.value,
|
||||
startDate.value,
|
||||
@@ -52,39 +64,36 @@ async function loadBars() {
|
||||
)
|
||||
if (bars.length < 2) {
|
||||
error.value = `该日期范围内仅取到 ${bars.length} 根 K 线,不足以回测`
|
||||
return
|
||||
store.error = error.value
|
||||
return false
|
||||
}
|
||||
const range = `${startDate.value} ~ ${endDate.value}`
|
||||
store.setOhlcv(bars, `${market.value}:${code.value} ${category.value} ${range}`)
|
||||
store.setOhlcv(bars, `${market}:${code.value} ${category.value} ${range}`)
|
||||
store.clearResult()
|
||||
return true
|
||||
} catch (e) {
|
||||
error.value = formatError(e)
|
||||
store.error = error.value
|
||||
return false
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 暴露给父组件(BacktestView / OptimizeView)在「开始回测/寻优」时串联调用
|
||||
defineExpose({ loadBars, loading })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="symbol-picker">
|
||||
<div class="row">
|
||||
<div class="field">
|
||||
<label>市场</label>
|
||||
<select v-model="market">
|
||||
<option value="SZ">深市</option>
|
||||
<option value="SH">沪市</option>
|
||||
<option value="BJ">北交所</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field code-field">
|
||||
<label>代码</label>
|
||||
<input
|
||||
v-model="code"
|
||||
maxlength="6"
|
||||
placeholder="6位代码"
|
||||
@keyup.enter="loadBars"
|
||||
/>
|
||||
</div>
|
||||
<div class="field code-field">
|
||||
<label>代码</label>
|
||||
<input
|
||||
v-model="code"
|
||||
maxlength="6"
|
||||
placeholder="6位代码(市场自动识别)"
|
||||
/>
|
||||
<span v-if="detectedMarket" class="market-tag">{{ detectedMarket }}</span>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
@@ -105,10 +114,6 @@ async function loadBars() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="primary" :disabled="loading" @click="loadBars">
|
||||
{{ loading ? '取行情中…' : '取行情' }}
|
||||
</button>
|
||||
|
||||
<p v-if="error" class="err">{{ error }}</p>
|
||||
<p v-if="store.barsSource" class="ok">
|
||||
已加载:{{ store.barsSource }}({{ store.ohlcv.length }} 根)
|
||||
@@ -118,7 +123,21 @@ async function loadBars() {
|
||||
|
||||
<style scoped>
|
||||
.code-field {
|
||||
flex: 2;
|
||||
position: relative;
|
||||
}
|
||||
.code-field input {
|
||||
padding-right: 70px;
|
||||
}
|
||||
.market-tag {
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
bottom: 8px;
|
||||
font-size: 11px;
|
||||
color: var(--text-dim);
|
||||
background: var(--bg-elevated);
|
||||
border: 1px solid var(--border);
|
||||
padding: 1px 6px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.err {
|
||||
color: var(--up);
|
||||
|
||||
Reference in New Issue
Block a user