diff --git a/backend/app/themes.py b/backend/app/themes.py index ef5dc90..fbe35fd 100644 --- a/backend/app/themes.py +++ b/backend/app/themes.py @@ -17,6 +17,7 @@ were the same timestamp — see `frequency` on each scored theme. from __future__ import annotations +import statistics from dataclasses import dataclass from typing import Optional @@ -200,8 +201,22 @@ def symbol_breakdown( d = fac.get("dividend_yield") or 0.0 g = float(g) if g is not None else 0.0 raw_siamchart = g + d * 2.0 - # z-score against the full universe (same as build_siamchart_score) - siamchart_score = build_siamchart_score(factor_view).get(symbol, 0.0) + + # z-score against the full universe (same as build_siamchart_score); capture + # the population stats so the view can show HOW -2.8 became -0.588. + siamchart_map = build_siamchart_score(factor_view) + siamchart_score = siamchart_map.get(symbol, 0.0) + # recompute the population of raw scores to expose mean / stdev + raw_values = [] + for f in factor_view.get("factors", []): + if not f.get("symbol"): + continue + gg = f.get("eps_growth_yoy") + dd = f.get("dividend_yield") or 0.0 + gg = float(gg) if gg is not None else 0.0 + raw_values.append(gg + dd * 2.0) + pop_mean = statistics.mean(raw_values) if raw_values else 0.0 + pop_stdev = statistics.pstdev(raw_values) if raw_values else 0.0 combined = round(weight_theme * theme_score + weight_siamchart * siamchart_score, 3) @@ -217,7 +232,22 @@ def symbol_breakdown( "dividend_yield": fac.get("dividend_yield"), "raw_growth_plus_yield_2x": round(raw_siamchart, 3), }, + "siamchart_z_note": { + "formula": "z = (raw_i - mean) / stdev", + "raw_i": round(raw_siamchart, 3), + "population_mean": round(pop_mean, 3), + "population_stdev": round(pop_stdev, 3), + "universe_size": len(raw_values), + }, + "combined_calc": [ + {"label": "คะแนนธีม", "value": round(theme_score, 3), "weight": weight_theme, + "note": "ค่าเฉลี่ยของ theme surprise ที่หุ้นนี้อยู่ใน", "term": f"{theme_score:.3f} × {weight_theme}"}, + {"label": "คะแนนพื้นฐาน", "value": round(siamchart_score, 3), "weight": weight_siamchart, + "note": f"z-score ของ ((EPS growth {g:+.1f}%) + ปันผล {d:.1f}%×2) เมื่อเทียบทั้ง universe", + "term": f"{siamchart_score:.3f} × {weight_siamchart}"}, + ], "combined_score": combined, + "combined_formula": f"({theme_score:.3f} × {weight_theme}) + ({siamchart_score:.3f} × {weight_siamchart}) = {combined}", "weights": {"theme": weight_theme, "siamchart": weight_siamchart}, "fundamentals": { "pe": fac.get("pe"), diff --git a/frontend/src/App.vue b/frontend/src/App.vue index ab910e1..cfd4de9 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -93,6 +93,7 @@ const filteredFactorRows = computed(() => { // Numeric accessor used for sorting columns. function factorValue(row, key) { if (key === 'signal_score') return row.signal_score ?? (row.signal_side === 'LONG' ? 9999 : 0) + if (key === 'combined') return boardBySymbol.value[row.symbol]?.combined ?? -9999 if (key === 'symbol') return row.symbol if (key === 'dividend_yield') return row.dividend_yield ?? -1 if (key === 'eps_growth_yoy') return row.eps_growth_yoy ?? -1 @@ -418,7 +419,7 @@ onMounted(loadDashboard)
ภาพรวม alternative factors ไทย ไปจนถึงสัญญาณลงทุนที่อธิบายได้ — research + paper only
@@ -679,14 +680,17 @@ onMounted(loadDashboard)| คะแนนธีม (ธีม {{ symbolDetail.themes?.length || 0 }} ธีม) | {{ formatNumber(symbolDetail.theme_score) }} | × {{ symbolDetail.weights?.theme }} |
| คะแนนพื้นฐาน (EPS + yield) | {{ formatNumber(symbolDetail.siamchart_score) }} | × {{ symbolDetail.weights?.siamchart }} |
| คะแนนรวม = 60%×ธีม + 40%×พื้นฐาน | {{ formatNumber(symbolDetail.combined_score) }} |