feat(ui): real forward-test lifecycle panel (not cosmetic)
Forward mode in the simulation tab now calls the durable /api/v1/forward lifecycle instead of the single-pass /api/v1/simulation (which only differed by a mode string). runSimulation branches to /api/v1/forward for forward mode, loads runs on mount, and renders a forward panel listing each paper run with its status (frozen/executed/marked/matured), non-PIT badge, holdings and a Mark / Mature action per run. Frontend build passes (bundle index-INuvPOCF.js).
This commit is contained in:
@@ -259,11 +259,21 @@ async function runSimulation() {
|
||||
simLoading.value = true
|
||||
simResult.value = null
|
||||
try {
|
||||
simResult.value = await fetchJson('/api/v1/simulation', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ capital: Number(simCapital.value), mode: simMode.value }),
|
||||
})
|
||||
if (simMode.value === 'forward') {
|
||||
// REAL forward lifecycle (not cosmetic): freeze + execute a paper run.
|
||||
simResult.value = await fetchJson('/api/v1/forward', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ capital: Number(simCapital.value), use_pit: false }),
|
||||
})
|
||||
await loadForward()
|
||||
} else {
|
||||
simResult.value = await fetchJson('/api/v1/simulation', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ capital: Number(simCapital.value), mode: 'backtest' }),
|
||||
})
|
||||
}
|
||||
} catch (caught) {
|
||||
notice.value = caught.message
|
||||
} finally {
|
||||
@@ -271,6 +281,38 @@ async function runSimulation() {
|
||||
}
|
||||
}
|
||||
|
||||
// ---- real forward-test lifecycle (durable paper runs) ----
|
||||
const fwdRuns = ref([])
|
||||
const fwdLoading = ref(false)
|
||||
|
||||
async function loadForward() {
|
||||
fwdLoading.value = true
|
||||
try {
|
||||
const body = await fetchJson('/api/v1/forward')
|
||||
fwdRuns.value = body.runs ?? []
|
||||
} catch (caught) {
|
||||
notice.value = caught.message
|
||||
} finally {
|
||||
fwdLoading.value = false
|
||||
}
|
||||
}
|
||||
async function markForward(id) {
|
||||
try {
|
||||
await fetchJson(`/api/v1/forward/${id}/mark`, { method: 'POST' })
|
||||
await loadForward()
|
||||
} catch (caught) {
|
||||
notice.value = caught.message
|
||||
}
|
||||
}
|
||||
async function matureForward(id) {
|
||||
try {
|
||||
await fetchJson(`/api/v1/forward/${id}/mature`, { method: 'POST' })
|
||||
await loadForward()
|
||||
} catch (caught) {
|
||||
notice.value = caught.message
|
||||
}
|
||||
}
|
||||
|
||||
async function loadDashboard() {
|
||||
loading.value = true
|
||||
error.value = ''
|
||||
@@ -300,6 +342,7 @@ async function loadDashboard() {
|
||||
paperAuthWarning.value = sessionBody.warning || ''
|
||||
backtest.value = backtestBody
|
||||
researchRun.value = researchBody
|
||||
await loadForward() // load durable forward-test runs (not cosmetic)
|
||||
} catch (caught) {
|
||||
error.value = caught.message
|
||||
} finally {
|
||||
@@ -679,6 +722,34 @@ onMounted(async () => { await loadDashboard(); await loadBacktestRuns() })
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!simResult" class="empty-research">กด 'คำนวณการจัดสรร' เพื่อดูว่า 50/20/30 จัดสรรทุนของคุณไปที่หุ้นไหนบ้าง</div>
|
||||
|
||||
<!-- REAL forward-test lifecycle panel (not cosmetic) -->
|
||||
<div v-if="simMode === 'forward'" class="fwd-panel">
|
||||
<div class="section-kicker">Forward Test (Paper) — สัญญาณถูกตรึง ณ เวลาสร้าง</div>
|
||||
<p class="panel-subtitle">สร้าง forward run → สัญญาณ (คะแนน) ถูก freeze ทันทีที่สร้าง แล้ว execute ด้วยราคาหลัง freeze. กด Mark ตามราคาล่าสุด, Mature เพื่อปิด run. เป็น Paper เท่านั้น.</p>
|
||||
<div v-if="fwdRuns.length === 0" class="empty-research muted-cell">ยังไม่มี forward run — กด 'คำนวณการจัดสรร' ข้างบนเพื่อสร้าง</div>
|
||||
<table v-else class="source-table">
|
||||
<thead><tr><th>#</th><th>สถานะ</th><th>ทุน</th><th>ลงทุน</th><th>ถือ</th><th>ผลตอบแทน</th><th>ตรวจ</th></tr></thead>
|
||||
<tbody>
|
||||
<tr v-for="run in fwdRuns.slice().reverse()" :key="run.id">
|
||||
<td class="muted-cell">{{ run.id.slice(0, 12) }}</td>
|
||||
<td>
|
||||
<span class="status-tag" :class="run.status === 'matured' ? 'warning-tag' : (run.status === 'frozen' ? 'neutral-tag' : 'warning-tag')">{{ run.status }}</span>
|
||||
<span v-if="run.non_pit" class="status-tag warning-tag" title="ใช้คะแนนปัจจุบัน ไม่ใช่ PIT">non-PIT</span>
|
||||
</td>
|
||||
<td>{{ formatNumber(run.capital, 0) }}</td>
|
||||
<td class="positive-text">{{ formatNumber(run.invested, 0) }}</td>
|
||||
<td class="muted-cell">{{ Object.keys(run.holdings || {}).join(', ') || '—' }}</td>
|
||||
<td :class="pnlClass(run.net_return)">{{ run.net_return != null ? (run.net_return * 100).toFixed(2) + '%' : '—' }}</td>
|
||||
<td>
|
||||
<button v-if="run.status !== 'matured'" class="primary-btn" style="padding:2px 8px;margin-right:4px" @click="markForward(run.id)">Mark</button>
|
||||
<button v-if="run.status !== 'matured'" class="primary-btn" style="padding:2px 8px" @click="matureForward(run.id)">Mature</button>
|
||||
<span v-else class="muted-cell">ปิดแล้ว</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel backtest-panel" id="backtest">
|
||||
|
||||
Reference in New Issue
Block a user