feat(front+api): #79+#94+#95验收三连——#79:策略库卡片收益格移除(用户'回测·最新10.44%没啥用',回测数字口径易误导,收益看各列表/全景;latestRet/retInfoOf孤儿函数随删,pct/retClass巡检仍在);#94:实盘列表标的列接poolLabel(all→全市场)+实例列建instanceNames映射显实例名(原绑row.instance但后端list_lives从未填恒'—',对齐模拟盘#71口径,兜底实例#id);#95:标的池默认值统一改all全市场——前端三表单(paper/live/PortfolioBacktest poolForm)+后端四处默认(routes_live setdefault/routes_portfolio Field/routes_paper/orchestrator runner)同步对齐防口径分裂,标签'全市场(慢,非MVP)'简化'全市场';936绿+build绿 [vps]
This commit is contained in:
@@ -55,7 +55,7 @@ const dr = loadDateRange()
|
||||
// §12.6 选档案(跳转带入或页面下拉),提交带 instance_id 回写四格
|
||||
const selectedInstanceId = ref<number | null>(null)
|
||||
const form = reactive({
|
||||
pool: 'hs300_subset',
|
||||
pool: 'all',
|
||||
strategy: 'all_weather',
|
||||
max_pool: 30,
|
||||
start: dr.start,
|
||||
@@ -81,7 +81,7 @@ watch(
|
||||
|
||||
const poolOptions = [
|
||||
{ label: 'HS300 子集(小范围验证)', value: 'hs300_subset' },
|
||||
{ label: '全市场(慢,非 MVP)', value: 'all' },
|
||||
{ label: '全市场', value: 'all' },
|
||||
]
|
||||
const strategyOptions = [
|
||||
{ label: '全天候轮动', value: 'all_weather' },
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
import { ref, reactive, computed, onMounted, onUnmounted } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import CodeSnapshotDrawer from '@/views/strategy/CodeSnapshotDrawer.vue'
|
||||
import { poolLabel } from '@/constants/strategy'
|
||||
import { getInstances } from '@/api/strategy'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import {
|
||||
listLives, startLive, stopLive, deleteLive, updateLive,
|
||||
@@ -73,6 +75,21 @@ function num(v: number | null | undefined): string {
|
||||
return Math.round(v).toLocaleString('zh-CN')
|
||||
}
|
||||
|
||||
// #94 实例名映射(instance_id → 实例名,对齐模拟盘列表口径;后端 list_lives 不填 instance 字段)
|
||||
const instanceNames = ref<Record<number, string>>({})
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const ins = await getInstances()
|
||||
instanceNames.value = Object.fromEntries(ins.map((i) => [i.id, i.name]))
|
||||
} catch {
|
||||
/* 映射失败退化为 实例 #id */
|
||||
}
|
||||
})
|
||||
function instanceNameOf(instanceId: number | null | undefined): string {
|
||||
if (!instanceId) return '—'
|
||||
return instanceNames.value[instanceId] || `实例 #${instanceId}`
|
||||
}
|
||||
|
||||
function goMonitor(a: LiveAccount): void {
|
||||
router.push(`/live/monitor/${a.id}`)
|
||||
}
|
||||
@@ -253,7 +270,7 @@ async function onStop(a: LiveAccount): Promise<void> {
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="标的" min-width="110">
|
||||
<template #default="{ row }"><span class="mono">{{ row.vt_symbol }}</span></template>
|
||||
<template #default="{ row }"><span class="mono">{{ poolLabel(row.vt_symbol) }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="策略实例" min-width="160">
|
||||
<template #default="{ row }">
|
||||
@@ -261,8 +278,8 @@ async function onStop(a: LiveAccount): Promise<void> {
|
||||
<div class="cell-id mono muted">{{ row.strategy_class }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="instance" label="实例" min-width="160">
|
||||
<template #default="{ row }"><span class="mono">{{ row.instance || '—' }}</span></template>
|
||||
<el-table-column label="实例" min-width="160">
|
||||
<template #default="{ row }"><span class="mono">{{ instanceNameOf(row.instance_id) }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="频率" width="70">
|
||||
<template #default="{ row }"><span class="mono">{{ row.interval }}</span></template>
|
||||
|
||||
@@ -19,10 +19,10 @@ const isPortfolio = computed(() => strategyType.value === 'portfolio')
|
||||
|
||||
const portfolioOptions = ref<string[]>([])
|
||||
const portfolioStrategy = ref('all_weather')
|
||||
const poolForm = ref({ pool: 'hs300_subset', max_pool: 30, benchmark: '000300.XSHG' })
|
||||
const poolForm = ref({ pool: 'all', max_pool: 30, benchmark: '000300.XSHG' })
|
||||
const POOL_OPTIONS = [
|
||||
{ label: 'HS300 子集(小范围验证)', value: 'hs300_subset' },
|
||||
{ label: '全市场(慢,非 MVP)', value: 'all' },
|
||||
{ label: '全市场', value: 'all' },
|
||||
]
|
||||
const BENCH_OPTIONS = [
|
||||
{ label: '沪深300', value: '000300.XSHG' },
|
||||
|
||||
@@ -27,10 +27,10 @@ const instancesOfCta = computed(() => {
|
||||
const file = classToFile.value[form.value.strategies[0]?.name]
|
||||
return file ? instanceOptions.value.filter((i) => i.code_file === file) : instanceOptions.value
|
||||
})
|
||||
const poolForm = reactive({ pool: 'hs300_subset', max_pool: 30, benchmark: '000300.XSHG' })
|
||||
const poolForm = reactive({ pool: 'all', max_pool: 30, benchmark: '000300.XSHG' })
|
||||
const POOL_OPTIONS = [
|
||||
{ label: 'HS300 子集(小范围验证)', value: 'hs300_subset' },
|
||||
{ label: '全市场(慢,非 MVP)', value: 'all' },
|
||||
{ label: '全市场', value: 'all' },
|
||||
]
|
||||
const BENCH_OPTIONS = [
|
||||
{ label: '沪深300', value: '000300.XSHG' },
|
||||
|
||||
@@ -115,23 +115,6 @@ const lampOf = (s: string): string => {
|
||||
}
|
||||
const pct = (v: number | null | undefined): string => (v == null ? '—' : (v * 100).toFixed(2) + '%')
|
||||
const retClass = (v: number | null | undefined): string => (v == null ? '' : v >= 0 ? 'up' : 'down')
|
||||
// #75 收益数值与标签同源:运行类 ret 优先(含其类型),否则回测/回放 run_returns
|
||||
interface RetInfo { v: number | null; label: string }
|
||||
function retInfoOf(i: Instance): RetInfo {
|
||||
const runs = (i.running_accounts || []).filter((a) => a.ret != null)
|
||||
if (runs.length) {
|
||||
const last = runs[runs.length - 1]
|
||||
const label = last.kind === 'live' ? '实盘 · 最新' : last.kind === 'shadow' ? '影子 · 最新' : '模拟实走 · 最新'
|
||||
return { v: last.ret, label }
|
||||
}
|
||||
const rr = (i as Instance & { run_returns?: Record<string, number> }).run_returns || {}
|
||||
if (rr.backtest != null) return { v: rr.backtest, label: '回测 · 最新' }
|
||||
if (rr.replay != null) return { v: rr.replay, label: '回放 · 最新' }
|
||||
return { v: null, label: '未运行' }
|
||||
}
|
||||
function latestRet(i: Instance): number | null {
|
||||
return retInfoOf(i).v
|
||||
}
|
||||
const paramSummary = (p: Record<string, unknown>): string =>
|
||||
Object.entries(p).map(([k, v]) => `${k}=${v}`).join(' · ')
|
||||
|
||||
@@ -327,8 +310,7 @@ const runningRows = computed(() =>
|
||||
<span class="sub mono">#{{ i.id }} · {{ i.interval }} · 更新 {{ i.updated_at }}</span>
|
||||
</div>
|
||||
<div class="params mono muted">{{ paramSummary(i.params) }}</div>
|
||||
<!-- #91: 标的行移除——#86a 定稿实例不承载标的(发起时定),老实例存量值显示只会误导 -->
|
||||
<div class="ret mono" :class="retClass(latestRet(i))">{{ pct(latestRet(i)) }}<span class="sub">{{ retInfoOf(i).label }}</span></div>
|
||||
<!-- #91: 标的行移除;#79: 收益格移除——回测数字口径易误导,收益看各列表/全景(口径干净) -->
|
||||
<div class="runs">
|
||||
<button class="run-btn" @click="runGo(i, 'backtest')"><span class="lamp" :class="lampOf(i.status.backtest)"></span>回测{{ badge(i, 'backtest') }}</button>
|
||||
<button v-if="fileTypeOf(i) !== 'portfolio'" class="run-btn" @click="runGo(i, 'replay')"><span class="lamp" :class="lampOf(i.status.replay)"></span>回放{{ badge(i, 'replay') }}</button>
|
||||
|
||||
@@ -138,7 +138,7 @@ def create_live(req: LiveCreateRequest):
|
||||
# 组合实盘:vt_symbol 占位为池名;setting 存组合参数(supervisor 转发 env)
|
||||
if not payload.get("strategy_class"):
|
||||
raise HTTPException(400, "组合实盘需选择策略(strategy_class)")
|
||||
payload.setdefault("pool", "hs300_subset")
|
||||
payload.setdefault("pool", "all")
|
||||
payload.setdefault("max_pool", 30)
|
||||
payload.setdefault("benchmark", "000300.XSHG")
|
||||
payload["vt_symbol"] = payload["pool"]
|
||||
|
||||
@@ -61,7 +61,7 @@ class PaperCreateRequest(BaseModel):
|
||||
strategy_type: str = "cta"
|
||||
# 撮合引擎(影子柜台 P1):eod_replay=日终回放(NAS 20:30) / shadow=影子柜台(VPS 盘中实时)
|
||||
engine: str = "eod_replay"
|
||||
pool: str = "hs300_subset"
|
||||
pool: str = "all"
|
||||
max_pool: int = 30
|
||||
benchmark: str = "000300.XSHG"
|
||||
# §12.6 实例做实:账户绑档案;空=发起即建档(自动创建实例再发起)
|
||||
|
||||
@@ -29,7 +29,7 @@ async def verify_token(authorization: str | None = Header(None)):
|
||||
|
||||
|
||||
class PortfolioBacktestRequest(BaseModel):
|
||||
pool: str = Field(default="hs300_subset", description="标的池(占位,MVP 用默认)")
|
||||
pool: str = Field(default="all", description="标的池(all=全市场)")
|
||||
start_date: str = Field(default="2024-01-01", description="YYYY-MM-DD")
|
||||
end_date: str = Field(default="2024-02-29", description="YYYY-MM-DD")
|
||||
initial_cash: float = Field(default=1_000_000.0, description="初始资金(元)")
|
||||
|
||||
@@ -140,7 +140,7 @@ class Orchestrator:
|
||||
async def submit_portfolio(self, start: str, end: str, cash: float,
|
||||
benchmark: str, strategy: str = "all_weather",
|
||||
max_pool: int = 30,
|
||||
pool: str = "hs300_subset",
|
||||
pool: str = "all",
|
||||
provider_config=None,
|
||||
commission_rate: float = 0.0003,
|
||||
stamp_duty_rate: float = 0.001,
|
||||
|
||||
Reference in New Issue
Block a user