mirror of
https://ghfast.top/https://github.com/aeroxw/easy_tdx_max.git
synced 2026-09-12 14:34:18 +08:00
release: v1.17.10 — 一键寻优「查看」跳转携带完整行情上下文
- SymbolPicker code/category/startDate/endDate 改 defineModel 双向同步 - OptimizeView 两个「查看」按钮跳转 URL 附带 symbol/startDate/endDate/category - BacktestView onMounted 回填标的/周期/日期到 SymbolPicker 表单 - 向后兼容:老 URL(只有 strategy/params) 仍正常,缺失字段保持默认 - 前端 vue-tsc / vite build 通过,后端 870 单测全绿(无回归)
This commit is contained in:
@@ -13,17 +13,22 @@ import type { Category } from '../types'
|
||||
|
||||
const store = useBacktestStore()
|
||||
|
||||
const code = ref('000001')
|
||||
const category = ref<Category>('DAY')
|
||||
|
||||
// 日期默认:结束=今天,开始=3年前
|
||||
function isoDaysFromNow(days: number): string {
|
||||
const d = new Date()
|
||||
d.setDate(d.getDate() + days)
|
||||
return d.toISOString().slice(0, 10)
|
||||
}
|
||||
const endDate = ref(isoDaysFromNow(0))
|
||||
const startDate = ref(isoDaysFromNow(-365 * 3))
|
||||
// 代码 / 周期 / 日期通过 defineModel 与父组件双向同步:
|
||||
// 既允许父组件读取(如寻优页「查看」按钮拼 URL 带上这些值),
|
||||
// 也允许父组件写入(如回测页从 URL query 回填表单)。
|
||||
// 未绑定时取默认值,向后兼容。
|
||||
//
|
||||
// 注意:defineModel 的 default 不能引用本 <script setup> 内声明的局部函数
|
||||
// (编译期会被 hoist 到 setup() 外,此时函数还未定义),
|
||||
// 因此日期默认值用内联字面量表达式计算。
|
||||
const code = defineModel<string>('code', { default: '000001' })
|
||||
const category = defineModel<Category>('category', { default: 'DAY' })
|
||||
const startDate = defineModel<string>('startDate', {
|
||||
default: new Date(Date.now() - 365 * 3 * 86400_000).toISOString().slice(0, 10),
|
||||
})
|
||||
const endDate = defineModel<string>('endDate', {
|
||||
default: new Date().toISOString().slice(0, 10),
|
||||
})
|
||||
|
||||
const error = ref('')
|
||||
// loading 由父组件控制(回测/寻优时驱动),组件自身只暴露 loadBars
|
||||
|
||||
@@ -12,7 +12,7 @@ import MetricTable from '../components/MetricTable.vue'
|
||||
import StrategyPicker from '../components/StrategyPicker.vue'
|
||||
import SymbolPicker from '../components/SymbolPicker.vue'
|
||||
import TradeTable from '../components/TradeTable.vue'
|
||||
import type { ExecutionMode } from '../types'
|
||||
import type { Category, ExecutionMode } from '../types'
|
||||
import { useBacktestStore } from '../stores/backtest'
|
||||
|
||||
const store = useBacktestStore()
|
||||
@@ -21,6 +21,18 @@ const route = useRoute()
|
||||
// SymbolPicker 实例引用,用于触发取行情
|
||||
const symbolPicker = ref<InstanceType<typeof SymbolPicker> | null>(null)
|
||||
|
||||
// 镜像 SymbolPicker 的代码/周期/日期,与 SymbolPicker 通过 v-model 双向同步。
|
||||
// 初始值与 SymbolPicker 默认一致;onMounted 时若 URL query 带了寻优页传来的值则覆盖。
|
||||
const code = ref('000001')
|
||||
const category = ref<Category>('DAY')
|
||||
function isoDaysFromNow(days: number): string {
|
||||
const d = new Date()
|
||||
d.setDate(d.getDate() + days)
|
||||
return d.toISOString().slice(0, 10)
|
||||
}
|
||||
const startDate = ref(isoDaysFromNow(-365 * 3))
|
||||
const endDate = ref(isoDaysFromNow(0))
|
||||
|
||||
// 表单状态(v-model 给子组件)
|
||||
const strategy = ref('ma_cross')
|
||||
const params = ref<Record<string, number | string | boolean>>({})
|
||||
@@ -56,6 +68,17 @@ onMounted(async () => {
|
||||
// query 参数解析失败,忽略
|
||||
}
|
||||
}
|
||||
|
||||
// 从 URL query 回填标的代码 / 周期 / 日期范围(寻优页「查看」跳转带来)。
|
||||
// 各字段独立 if 守卫:老书签(只有 strategy/params)仍保持默认值,向后兼容。
|
||||
const qSymbol = route.query.symbol as string | undefined
|
||||
const qStartDate = route.query.startDate as string | undefined
|
||||
const qEndDate = route.query.endDate as string | undefined
|
||||
const qCategory = route.query.category as Category | undefined
|
||||
if (qSymbol) code.value = qSymbol
|
||||
if (qStartDate) startDate.value = qStartDate
|
||||
if (qEndDate) endDate.value = qEndDate
|
||||
if (qCategory) category.value = qCategory
|
||||
})
|
||||
|
||||
// 取行情 + 回测 串联(点击「开始回测」触发)
|
||||
@@ -82,7 +105,13 @@ async function onRun() {
|
||||
<aside class="config-panel">
|
||||
<section class="panel-section">
|
||||
<h3>行情数据</h3>
|
||||
<SymbolPicker ref="symbolPicker" />
|
||||
<SymbolPicker
|
||||
ref="symbolPicker"
|
||||
v-model:code="code"
|
||||
v-model:category="category"
|
||||
v-model:start-date="startDate"
|
||||
v-model:end-date="endDate"
|
||||
/>
|
||||
</section>
|
||||
|
||||
<section class="panel-section">
|
||||
|
||||
@@ -9,7 +9,7 @@ import OptimizeHeatmap from '../components/OptimizeHeatmap.vue'
|
||||
import OptimizeResultTable from '../components/OptimizeResultTable.vue'
|
||||
import ParamGridPicker from '../components/ParamGridPicker.vue'
|
||||
import SymbolPicker from '../components/SymbolPicker.vue'
|
||||
import type { ExecutionMode } from '../types'
|
||||
import type { Category, ExecutionMode } from '../types'
|
||||
import { useBacktestStore } from '../stores/backtest'
|
||||
|
||||
const store = useBacktestStore()
|
||||
@@ -18,6 +18,18 @@ const router = useRouter()
|
||||
// SymbolPicker 实例引用,用于触发取行情
|
||||
const symbolPicker = ref<InstanceType<typeof SymbolPicker> | null>(null)
|
||||
|
||||
// 镜像 SymbolPicker 的代码/周期/日期,用于「查看」跳转时拼进 URL query。
|
||||
// 与 SymbolPicker 通过 v-model 双向同步,初始值与 SymbolPicker 默认一致。
|
||||
const code = ref('000001')
|
||||
const category = ref<Category>('DAY')
|
||||
function isoDaysFromNow(days: number): string {
|
||||
const d = new Date()
|
||||
d.setDate(d.getDate() + days)
|
||||
return d.toISOString().slice(0, 10)
|
||||
}
|
||||
const startDate = ref(isoDaysFromNow(-365 * 3))
|
||||
const endDate = ref(isoDaysFromNow(0))
|
||||
|
||||
const strategy = ref('ma_cross')
|
||||
const paramGrid = ref<Record<string, Array<number | string>>>({})
|
||||
const cash = ref(1000000)
|
||||
@@ -83,21 +95,28 @@ async function onRunAll() {
|
||||
})
|
||||
}
|
||||
|
||||
/** 「查看」跳转时,把当前标的 + 周期 + 日期范围一并塞进 query,
|
||||
* 让回测页能完整复现寻优时的行情(而非只带策略参数)。 */
|
||||
function buildBacktestQuery(strategyName: string, params: Record<string, number | string>) {
|
||||
return {
|
||||
strategy: strategyName,
|
||||
params: JSON.stringify(params),
|
||||
symbol: code.value,
|
||||
startDate: startDate.value,
|
||||
endDate: endDate.value,
|
||||
category: category.value,
|
||||
}
|
||||
}
|
||||
|
||||
// 点击排名表「查看」→ 跳转单标的页用该参数回测
|
||||
function onViewParams(params: Record<string, number | string>) {
|
||||
// 通过 query 传递参数,单标的页接收后自动填充
|
||||
router.push({
|
||||
path: '/',
|
||||
query: { strategy: strategy.value, params: JSON.stringify(params) },
|
||||
})
|
||||
router.push({ path: '/', query: buildBacktestQuery(strategy.value, params) })
|
||||
}
|
||||
|
||||
// 一键寻优结果点击「查看」→ 跳转单标的页用该策略 + 参数回测
|
||||
function onViewAll(strategyName: string, params: Record<string, number | string>) {
|
||||
router.push({
|
||||
path: '/',
|
||||
query: { strategy: strategyName, params: JSON.stringify(params) },
|
||||
})
|
||||
router.push({ path: '/', query: buildBacktestQuery(strategyName, params) })
|
||||
}
|
||||
|
||||
function pct(v: number | null | undefined): string {
|
||||
@@ -113,7 +132,13 @@ function num(v: number | null | undefined, d = 2): string {
|
||||
<aside class="config-panel">
|
||||
<section class="panel-section">
|
||||
<h3>行情数据</h3>
|
||||
<SymbolPicker ref="symbolPicker" />
|
||||
<SymbolPicker
|
||||
ref="symbolPicker"
|
||||
v-model:code="code"
|
||||
v-model:category="category"
|
||||
v-model:start-date="startDate"
|
||||
v-model:end-date="endDate"
|
||||
/>
|
||||
</section>
|
||||
|
||||
<section class="panel-section">
|
||||
|
||||
Reference in New Issue
Block a user