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:
@@ -159,6 +159,23 @@ function dividendIsEstimate(method) {
|
|||||||
// only dated_ledger rows are real dated cash flows; everything else is an estimate/proxy
|
// only dated_ledger rows are real dated cash flows; everything else is an estimate/proxy
|
||||||
return method !== 'dated_ledger'
|
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) {
|
function formatDate(value) {
|
||||||
if (!value) return '—'
|
if (!value) return '—'
|
||||||
@@ -777,12 +794,12 @@ onMounted(async () => { await loadDashboard(); await loadBacktestRuns() })
|
|||||||
<div v-else-if="btResult" class="backtest-results">
|
<div v-else-if="btResult" class="backtest-results">
|
||||||
<div class="bt-kpi-grid">
|
<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="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>{{ 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 class="bt-kpi"><span>ผลตอบแทนสุทธิ</span><strong :class="pnlClass(btResult.net_return)">{{ (btResult.net_return * 100).toFixed(2) }}%</strong></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="bt-meta muted-cell">Trades: {{ btResult.trades }} · ช่วง {{ btResult.start }} → {{ btResult.end }}</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="!btResult.leakage_guard" class="bt-meta muted-cell">คำเตือน: ผลนี้ใช้คะแนนปัจจุบันย้อนหลัง จึงเป็น descriptive non-PIT และไม่ใช่หลักฐานประสิทธิภาพกลยุทธ์</div>
|
||||||
<div v-if="Object.keys(btResult.holdings || {}).length" class="bt-holdings">
|
<div v-if="Object.keys(btResult.holdings || {}).length" class="bt-holdings">
|
||||||
<strong>พอร์ตสุดท้าย:</strong>
|
<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>{{ 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>{{ formatNumber(r.capital) }}</td>
|
||||||
<td :class="pnlClass(r.price_pnl)">{{ formatNumber(r.price_pnl) }}</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="pnlClass(r.net_return)">{{ (r.net_return * 100).toFixed(2) }}%</td>
|
||||||
<td class="muted-cell">{{ r.ran_at ? formatDate(r.ran_at) : '—' }}</td>
|
<td class="muted-cell">{{ r.ran_at ? formatDate(r.ran_at) : '—' }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
Reference in New Issue
Block a user