feat(ui): disclose dividend method + honest estimate badge in backtest

Map dividend_method (dated_ledger / dps_annual_proxy / final_holdings_yield
_proxy) to an honest Thai label. Show a Proxy badge whenever the dividend is
an estimate (everything but dated_ledger) on both the result card and the
saved-run history rows, and set the footnote to match the actual method.
Frontend build passes.
This commit is contained in:
Kunthawat Greethong
2026-08-27 11:47:44 +07:00
parent 068dff22d7
commit 5b8b6d12b7

View File

@@ -149,6 +149,17 @@ function formatNumber(value, digits = 2) {
return Number(value ?? 0).toFixed(digits)
}
// map the backend's dividend_method to an honest Thai label/flag
function dividendMethodLabel(method) {
if (method === 'dated_ledger') return 'ปันผล (ตามวันจริง)'
if (method === 'dps_annual_proxy') return 'ประมาณการปันผล/หุ้น (DPS)'
return 'ประมาณการปันผล (Proxy)' // final_holdings_yield_proxy or unknown
}
function dividendIsEstimate(method) {
// only dated_ledger rows are real dated cash flows; everything else is an estimate/proxy
return method !== 'dated_ledger'
}
function formatDate(value) {
if (!value) return '—'
return new Date(value).toLocaleString('en-GB', {
@@ -695,12 +706,12 @@ onMounted(async () => { await loadDashboard(); await loadBacktestRuns() })
<div v-else-if="btResult" class="backtest-results">
<div class="bt-kpi-grid">
<div class="bt-kpi"><span>กำไรจากราคา</span><strong :class="pnlClass(btResult.price_pnl)">{{ formatNumber(btResult.price_pnl) }} บาท</strong></div>
<div class="bt-kpi"><span>ประมาณการปนผล*</span><strong class="positive-text">{{ formatNumber(btResult.dividend_income) }} บาท</strong></div>
<div class="bt-kpi"><span>{{ dividendMethodLabel(btResult.dividend_method) }}<span v-if="dividendIsEstimate(btResult.dividend_method)" class="status-tag warning-tag" style="margin-left:6px">Proxy</span></span><strong class="positive-text">{{ formatNumber(btResult.dividend_income) }} บาท</strong></div>
<div class="bt-kpi"><span>ลคาสดทาย</span><strong>{{ formatNumber(btResult.final_value) }} บาท</strong></div>
<div class="bt-kpi"><span>ผลตอบแทนสทธ</span><strong :class="pnlClass(btResult.net_return)">{{ (btResult.net_return * 100).toFixed(2) }}%</strong></div>
</div>
<div class="bt-meta muted-cell">Trades: {{ btResult.trades }} · วง {{ btResult.start }} {{ btResult.end }}</div>
<div class="bt-meta muted-cell">*ประมาณจาก dividend yield ของพอรตสดทาย ไมใชกระแสเงนสดปนผลจร</div>
<div class="bt-meta muted-cell">*{{ dividendIsEstimate(btResult.dividend_method) ? 'ประมาณการปันผล ไม่ใช่กระแสเงินสดปันผลตามวันจริง' : 'ปันผลตามวันจริงจาก ledger (ex-date/per-share)' }}</div>
<div v-if="!btResult.leakage_guard" class="bt-meta muted-cell">คำเตือน: ผลนี้ใช้คะแนนปัจจุบันย้อนหลัง จึงเป็น descriptive non-PIT และไม่ใช่หลักฐานประสิทธิภาพกลยุทธ์</div>
<div v-if="Object.keys(btResult.holdings || {}).length" class="bt-holdings">
<strong>พอรตสดทาย:</strong>
@@ -712,13 +723,13 @@ onMounted(async () => { await loadDashboard(); await loadBacktestRuns() })
<div v-if="btRuns.length" class="bt-history">
<div class="section-kicker">ประวการยอนทดสอบ</div>
<table class="source-table">
<thead><tr><th>#</th><th>วง</th><th></th><th>กำไรราคา</th><th>ระมาณการปนผล (Proxy)</th><th>ผลตอบแทน</th><th>นเม</th></tr></thead>
<thead><tr><th>#</th><th>วง</th><th></th><th>กำไรราคา</th><th>นผล</th><th>ผลตอบแทน</th><th>นเม</th></tr></thead>
<tbody>
<tr v-for="r in btRuns.slice().reverse()" :key="r.id">
<td>{{ r.id }}</td><td>{{ r.start }} {{ r.end }} <span v-if="r.leakage_guard === false" class="status-tag warning-tag" title="ใช้คะแนนปัจจุบันย้อนหลัง ไม่ใช่ point-in-time">descriptive non-PIT</span></td>
<td>{{ formatNumber(r.capital) }}</td>
<td :class="pnlClass(r.price_pnl)">{{ formatNumber(r.price_pnl) }}</td>
<td class="positive-text">{{ formatNumber(r.dividend_income) }}</td>
<td class="positive-text">{{ formatNumber(r.dividend_income) }}<span v-if="dividendIsEstimate(r.dividend_method)" class="status-tag warning-tag" title="ประมาณการ ไม่ใช่กระแสเงินสดจริง">Proxy</span></td>
<td :class="pnlClass(r.net_return)">{{ (r.net_return * 100).toFixed(2) }}%</td>
<td class="muted-cell">{{ r.ran_at ? formatDate(r.ran_at) : '—' }}</td>
</tr>