feat(ui): surface dated_ledger vs proxy synthesis in backtest results

Backtest result card and saved-run history now show an explicit dividend
badge per run: green 'ตามวันจริง' for dated_ledger (real ex-date x qty cash
flow), amber 'Proxy (ต่อหุ้น)'/'Proxy' for estimates, plus a footnote that
matches the actual dividend_method. Frontend build passes (bundle
index-DDItjwYy.js); served bundle contains the new strings.
This commit is contained in:
Kunthawat Greethong
2026-08-27 12:54:44 +07:00
parent 28c111234e
commit f3fad16183

View File

@@ -159,6 +159,23 @@ function dividendIsEstimate(method) {
// only dated_ledger rows are real dated cash flows; everything else is an estimate/proxy
return method !== 'dated_ledger'
}
function dividendIsDated(method) {
// real dated cash-flow ledger (per-share x qty on actual ex/pay dates)
return method === 'dated_ledger'
}
function dividendBadge(method) {
// cls: use classes known to exist in this project (status-tag/neutral-tag/
// warning-tag); dated gets an inline green style so "real cash flow" reads
// as clearly better than "estimate".
if (dividendIsDated(method)) return { label: 'ตามวันจริง', cls: 'status-tag', style: 'background:#1a7f37;color:#fff' }
if (method === 'dps_annual_proxy') return { label: 'Proxy (ต่อหุ้น)', cls: 'status-tag warning-tag' }
return { label: 'Proxy', cls: 'status-tag warning-tag' }
}
function dividendFootnote(method) {
if (dividendIsDated(method)) return 'ปันผลตามวันจริงจาก ledger (ex-date × จำนวนหุ้น) — กระแสเงินสดจริง'
if (method === 'dps_annual_proxy') return 'ประมาณการปันผลต่อหุ้น (DPS ล่าสุด × จำนวนหุ้น) ไม่ใช่กระแสเงินสดตามวันจริง'
return 'ประมาณจาก dividend yield ของพอร์ตสุดท้าย ไม่ใช่กระแสเงินสดปันผลจริง'
}
function formatDate(value) {
if (!value) return '—'
@@ -777,12 +794,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>{{ 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>{{ dividendMethodLabel(btResult.dividend_method) }}<span class="status-tag" :class="dividendBadge(btResult.dividend_method).cls" :style="dividendBadge(btResult.dividend_method).style || undefined" style="margin-left:6px">{{ dividendBadge(btResult.dividend_method).label }}</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">*{{ dividendIsEstimate(btResult.dividend_method) ? 'ประมาณการปันผล ไม่ใช่กระแสเงินสดปันผลตามวันจริง' : 'ปันผลตามวันจริงจาก ledger (ex-date/per-share)' }}</div>
<div class="bt-meta muted-cell">*{{ dividendFootnote(btResult.dividend_method) }}</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>
@@ -800,7 +817,7 @@ onMounted(async () => { await loadDashboard(); await loadBacktestRuns() })
<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) }}<span v-if="dividendIsEstimate(r.dividend_method)" class="status-tag warning-tag" title="ประมาณการ ไม่ใช่กระแสเงินสดจริง">Proxy</span></td>
<td class="positive-text">{{ formatNumber(r.dividend_income) }}<span class="status-tag" :class="dividendBadge(r.dividend_method).cls" :style="dividendBadge(r.dividend_method).style || undefined" style="margin-left:4px" :title="dividendFootnote(r.dividend_method)">{{ dividendBadge(r.dividend_method).label }}</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>