[verified] Localize dashboard to Thai (navbar, KPI, section titles, headers); fix fixture badge honesty
- Sidebar nav all Thai (ภาพรวม/ธีม/ตารางหุ้น/สัญญาณ/ปัจจัย/ที่มาข้อมูล/งานวิจัย) - KPI labels Thai (สัญญาณที่ใช้งาน/คุณภาพข้อมูล/ประตู backtest/สมุดบันทึก paper) - Header H1 -> SET50 Signal Lab (multi-theme, not tourism-only); Thai subtitle - Section titles -> Thai (สิ่งที่เปลี่ยนไป/ที่มาของข้อมูล/ผลลัพธ์เชิงกำหนด/Research run/บันทึกการวิเคราะห์) - FIX: badge now shows ข้อมูลจำลอง (fixture) consistent with data-quality card (was wrongly 'ข้อมูลจริง') - Stock board header -> ตารางหุ้น (pure Thai); technical terms (theme surprise, paper, backtest gate) kept EN per user - Verified: npm build + browser full-page; all Thai labels render
This commit is contained in:
@@ -305,40 +305,41 @@ onMounted(loadDashboard)
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<nav class="nav-stack" aria-label="Primary navigation">
|
<nav class="nav-stack" aria-label="Primary navigation">
|
||||||
<a class="nav-item active" href="#overview"><span class="nav-glyph">◈</span>Overview</a>
|
<a class="nav-item active" href="#overview"><span class="nav-glyph">◈</span>ภาพรวม</a>
|
||||||
<a class="nav-item" href="#stocks"><span class="nav-glyph">▤</span>Stock board</a>
|
<a class="nav-item" href="#themes"><span class="nav-glyph">▦</span>ธีม</a>
|
||||||
<a class="nav-item" href="#signals"><span class="nav-glyph">↗</span>Signal board</a>
|
<a class="nav-item" href="#stocks"><span class="nav-glyph">▤</span>ตารางหุ้น</a>
|
||||||
<a class="nav-item" href="#factors"><span class="nav-glyph">∿</span>Factor explorer</a>
|
<a class="nav-item" href="#signals"><span class="nav-glyph">↗</span>สัญญาณ</a>
|
||||||
<a class="nav-item" href="#lineage"><span class="nav-glyph">⌁</span>Data lineage</a>
|
<a class="nav-item" href="#factors"><span class="nav-glyph">∿</span>ปัจจัย</a>
|
||||||
<a class="nav-item" href="#research-run"><span class="nav-glyph">✓</span>Research run</a>
|
<a class="nav-item" href="#lineage"><span class="nav-glyph">⌁</span>ที่มาข้อมูล</a>
|
||||||
|
<a class="nav-item" href="#research-run"><span class="nav-glyph">✓</span>งานวิจัย</a>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div class="sidebar-footer">
|
<div class="sidebar-footer">
|
||||||
<div class="mode-card">
|
<div class="mode-card">
|
||||||
<div class="mode-dot"></div>
|
<div class="mode-dot"></div>
|
||||||
<div>
|
<div>
|
||||||
<div class="mode-label">Research mode</div>
|
<div class="mode-label">โหมด Research</div>
|
||||||
<div class="mode-detail">Paper execution only</div>
|
<div class="mode-detail">Paper execution only</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="version-line">Tourism v0.5 · research + paper</div>
|
<div class="version-line">Multi-theme · research + paper</div>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<main class="content" id="overview">
|
<main class="content" id="overview">
|
||||||
<header class="topbar">
|
<header class="topbar">
|
||||||
<div>
|
<div>
|
||||||
<div class="eyebrow">Alternative data / SET50</div>
|
<div class="eyebrow">Alternative data · SET50</div>
|
||||||
<h1>Tourism Pulse</h1>
|
<h1>SET50 Signal Lab</h1>
|
||||||
<p class="subtitle">A first vertical slice from economic observation to explainable portfolio signal.</p>
|
<p class="subtitle">ภาพรวม alternative factors ไทย ไปจนถึงสัญญาณลงทุนที่อธิบายได้ — research + paper only</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="topbar-meta">
|
<div class="topbar-meta">
|
||||||
<div class="freshness-pill"><span class="freshness-dot"></span>{{ summary?.data_health?.source_mode === 'bot' ? 'BOT live vintage' : 'Fixture dataset' }}</div>
|
<div class="freshness-pill"><span class="freshness-dot"></span>{{ summary?.data_health?.source_mode === 'bot' ? 'ข้อมูลจริง BOT' : 'ข้อมูลจำลอง (fixture)' }}</div>
|
||||||
<div class="as-of">As of {{ summary?.as_of || '—' }}</div>
|
<div class="as-of">ข้อมูล {{ summary?.as_of || '—' }}</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div v-if="loading" class="state-card">Loading the signal snapshot…</div>
|
<div v-if="loading" class="state-card">กำลังโหลดข้อมูล…</div>
|
||||||
<div v-else-if="error" class="state-card error-state">{{ error }}</div>
|
<div v-else-if="error" class="state-card error-state">{{ error }}</div>
|
||||||
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
@@ -346,27 +347,27 @@ onMounted(loadDashboard)
|
|||||||
<article class="kpi-card accent-card">
|
<article class="kpi-card accent-card">
|
||||||
<div class="kpi-label">Theme surprise</div>
|
<div class="kpi-label">Theme surprise</div>
|
||||||
<div class="kpi-value">{{ summary.theme_surprise > 0 ? '+' : '' }}{{ formatNumber(summary.theme_surprise) }}<span class="kpi-unit">σ</span></div>
|
<div class="kpi-value">{{ summary.theme_surprise > 0 ? '+' : '' }}{{ formatNumber(summary.theme_surprise) }}<span class="kpi-unit">σ</span></div>
|
||||||
<div class="kpi-foot" :class="summary.theme_surprise >= 0 ? 'positive-text' : 'negative-text'">{{ summary.theme_surprise >= 0 ? 'Above' : 'Below' }} seasonal expectation</div>
|
<div class="kpi-foot" :class="summary.theme_surprise >= 0 ? 'positive-text' : 'negative-text'">{{ summary.theme_surprise >= 0 ? 'สูงกว่า' : 'ต่ำกว่า' }} ค่าตามฤดูกาล</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="kpi-card">
|
<article class="kpi-card">
|
||||||
<div class="kpi-label">Active signals</div>
|
<div class="kpi-label">สัญญาณที่ใช้งาน</div>
|
||||||
<div class="kpi-value">{{ summary.signal_summary.total }}</div>
|
<div class="kpi-value">{{ summary.signal_summary.total }}</div>
|
||||||
<div class="kpi-foot"><span class="long-count">{{ summary.signal_summary.long }} long</span> · <span class="short-count">{{ summary.signal_summary.short }} short</span></div>
|
<div class="kpi-foot"><span class="long-count">{{ summary.signal_summary.long }} ซื้อ</span> · <span class="short-count">{{ summary.signal_summary.short }} ขาย</span></div>
|
||||||
</article>
|
</article>
|
||||||
<article class="kpi-card">
|
<article class="kpi-card">
|
||||||
<div class="kpi-label">Paper ledger</div>
|
<div class="kpi-label">สมุดบันทึก paper</div>
|
||||||
<div class="kpi-value">{{ ledger.entries.length }}</div>
|
<div class="kpi-value">{{ ledger.entries.length }}</div>
|
||||||
<div class="kpi-foot">No external receiver connected</div>
|
<div class="kpi-foot">ไม่มีการเชื่อมต่อ external</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="kpi-card">
|
<article class="kpi-card">
|
||||||
<div class="kpi-label">Data quality</div>
|
<div class="kpi-label">คุณภาพข้อมูล</div>
|
||||||
<div class="kpi-value quality-value">{{ displayLabel(summary.data_health.status) }}</div>
|
<div class="kpi-value quality-value">{{ displayLabel(summary.data_health.status) }}</div>
|
||||||
<div class="kpi-foot">Vintage {{ summary.data_health.vintage_id }}</div>
|
<div class="kpi-foot">Vintage {{ summary.data_health.vintage_id }}</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="kpi-card">
|
<article class="kpi-card">
|
||||||
<div class="kpi-label">Backtest gate</div>
|
<div class="kpi-label">ประตู backtest (gate)</div>
|
||||||
<div class="kpi-value quality-value">{{ displayLabel(backtest?.status) }}</div>
|
<div class="kpi-value quality-value">{{ displayLabel(backtest?.status) }}</div>
|
||||||
<div class="kpi-foot">{{ backtest?.available_events || 0 }} / {{ backtest?.required_events || 0 }} vintages · {{ displayLabel(backtest?.price_snapshot?.quality) || 'price snapshot missing' }}</div>
|
<div class="kpi-foot">{{ backtest?.available_events || 0 }} / {{ backtest?.required_events || 0 }} vintages · {{ displayLabel(backtest?.price_snapshot?.quality) || 'ไม่มี price snapshot' }}</div>
|
||||||
</article>
|
</article>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -412,28 +413,28 @@ onMounted(loadDashboard)
|
|||||||
<div class="panel-header signal-header">
|
<div class="panel-header signal-header">
|
||||||
<div>
|
<div>
|
||||||
<div class="section-kicker">Alternative factors × SET50</div>
|
<div class="section-kicker">Alternative factors × SET50</div>
|
||||||
<h2>Stock board</h2>
|
<h2>ตารางหุ้น</h2>
|
||||||
<p class="panel-subtitle">Fundamentals from Siamchart merged with the tourism signal. Click a column header to sort; toggle <em>dividend only</em> to view dividend payers.</p>
|
<p class="panel-subtitle">มูลค่าพื้นฐานจาก Siamchart รวมกับสัญญาณ tourism — คลิกหัวตารางเพื่อเรียง; เปิด <em>เฉพาะหุ้นปันผล</em> เพื่อกรองหุ้นที่จ่ายปันผล.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="stock-controls">
|
<div class="stock-controls">
|
||||||
<label class="toggle-filter">
|
<label class="toggle-filter">
|
||||||
<input type="checkbox" v-model="dividendOnly" />
|
<input type="checkbox" v-model="dividendOnly" />
|
||||||
<span>Dividend only ({{ dividendCount }})</span>
|
<span>เฉพาะหุ้นปันผล ({{ dividendCount }})</span>
|
||||||
</label>
|
</label>
|
||||||
<span class="status-tag" :class="factorAvailable ? '' : 'warning-tag'">{{ factorAvailable ? 'Siamchart live' : 'factors unavailable' }}</span>
|
<span class="status-tag" :class="factorAvailable ? '' : 'warning-tag'">{{ factorAvailable ? 'Siamchart ใช้งานได้' : 'ไม่มี factor' }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!factorAvailable" class="empty-research">Siamchart factor snapshot is not available on disk. Run <code>collect_siamchart.py --group SET50 --with-info</code> to collect it.</div>
|
<div v-if="!factorAvailable" class="empty-research">Siamchart snapshot ไม่อยู่บน disk. รัน <code>collect_siamchart.py --group SET50 --with-info</code> เพื่อเก็บข้อมูล.</div>
|
||||||
<div v-else class="table-wrap">
|
<div v-else class="table-wrap">
|
||||||
<table class="factor-table">
|
<table class="factor-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="sortable" :class="{ active: sortKey === 'signal_score' }" @click="setSort('signal_score')">Signal {{ sortIndicator('signal_score') }}</th>
|
<th class="sortable" :class="{ active: sortKey === 'signal_score' }" @click="setSort('signal_score')">สัญญาณ {{ sortIndicator('signal_score') }}</th>
|
||||||
<th class="sortable" :class="{ active: sortKey === 'symbol' }" @click="setSort('symbol')">Symbol {{ sortIndicator('symbol') }}</th>
|
<th class="sortable" :class="{ active: sortKey === 'symbol' }" @click="setSort('symbol')">หุ้น {{ sortIndicator('symbol') }}</th>
|
||||||
<th class="sortable" :class="{ active: sortKey === 'pe' }" @click="setSort('pe')">P/E {{ sortIndicator('pe') }}</th>
|
<th class="sortable" :class="{ active: sortKey === 'pe' }" @click="setSort('pe')">P/E {{ sortIndicator('pe') }}</th>
|
||||||
<th class="sortable" :class="{ active: sortKey === 'eps' }" @click="setSort('eps')">EPS {{ sortIndicator('eps') }}</th>
|
<th class="sortable" :class="{ active: sortKey === 'eps' }" @click="setSort('eps')">EPS {{ sortIndicator('eps') }}</th>
|
||||||
<th class="sortable" :class="{ active: sortKey === 'eps_growth_yoy' }" @click="setSort('eps_growth_yoy')">EPS YoY {{ sortIndicator('eps_growth_yoy') }}</th>
|
<th class="sortable" :class="{ active: sortKey === 'eps_growth_yoy' }" @click="setSort('eps_growth_yoy')">EPS YoY {{ sortIndicator('eps_growth_yoy') }}</th>
|
||||||
<th class="sortable" :class="{ active: sortKey === 'dividend_yield' }" @click="setSort('dividend_yield')">Yield % {{ sortIndicator('dividend_yield') }}</th>
|
<th class="sortable" :class="{ active: sortKey === 'dividend_yield' }" @click="setSort('dividend_yield')">ปันผล % {{ sortIndicator('dividend_yield') }}</th>
|
||||||
<th class="sortable" :class="{ active: sortKey === 'pbv' }" @click="setSort('pbv')">P/BV {{ sortIndicator('pbv') }}</th>
|
<th class="sortable" :class="{ active: sortKey === 'pbv' }" @click="setSort('pbv')">P/BV {{ sortIndicator('pbv') }}</th>
|
||||||
<th class="sortable" :class="{ active: sortKey === 'roe' }" @click="setSort('roe')">ROE {{ sortIndicator('roe') }}</th>
|
<th class="sortable" :class="{ active: sortKey === 'roe' }" @click="setSort('roe')">ROE {{ sortIndicator('roe') }}</th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -445,7 +446,7 @@ onMounted(loadDashboard)
|
|||||||
<td class="score-cell">{{ factor.pe != null ? formatNumber(factor.pe) : '—' }}</td>
|
<td class="score-cell">{{ factor.pe != null ? formatNumber(factor.pe) : '—' }}</td>
|
||||||
<td>{{ factor.eps != null ? formatNumber(factor.eps) : '—' }}</td>
|
<td>{{ factor.eps != null ? formatNumber(factor.eps) : '—' }}</td>
|
||||||
<td :class="factor.eps_growth_yoy >= 0 ? 'positive-text' : 'negative-text'">{{ factor.eps_growth_yoy != null ? (factor.eps_growth_yoy >= 0 ? '+' : '') + formatNumber(factor.eps_growth_yoy) + '%' : '—' }}</td>
|
<td :class="factor.eps_growth_yoy >= 0 ? 'positive-text' : 'negative-text'">{{ factor.eps_growth_yoy != null ? (factor.eps_growth_yoy >= 0 ? '+' : '') + formatNumber(factor.eps_growth_yoy) + '%' : '—' }}</td>
|
||||||
<td :class="factor.dividend_yield >= 0 ? 'positive-text' : ''">{{ factor.dividend_yield != null ? formatNumber(factor.dividend_yield) + '%' : '—' }}<span v-if="factor.is_dividend" class="dividend-dot" title="Pays dividend">●</span></td>
|
<td :class="factor.dividend_yield >= 0 ? 'positive-text' : ''">{{ factor.dividend_yield != null ? formatNumber(factor.dividend_yield) + '%' : '—' }}<span v-if="factor.is_dividend" class="dividend-dot" title="จ่ายปันผล">●</span></td>
|
||||||
<td>{{ factor.pbv != null ? formatNumber(factor.pbv) : '—' }}</td>
|
<td>{{ factor.pbv != null ? formatNumber(factor.pbv) : '—' }}</td>
|
||||||
<td :class="factor.roe >= 0 ? 'positive-text' : 'negative-text'">{{ factor.roe != null ? formatNumber(factor.roe) + '%' : '—' }}</td>
|
<td :class="factor.roe >= 0 ? 'positive-text' : 'negative-text'">{{ factor.roe != null ? formatNumber(factor.roe) + '%' : '—' }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -458,14 +459,14 @@ onMounted(loadDashboard)
|
|||||||
<article class="panel pulse-panel">
|
<article class="panel pulse-panel">
|
||||||
<div class="panel-header">
|
<div class="panel-header">
|
||||||
<div>
|
<div>
|
||||||
<div class="section-kicker">01 / Economic pulse</div>
|
<div class="section-kicker">01 / สิ่งที่เปลี่ยนไป</div>
|
||||||
<h2>What changed?</h2>
|
<h2>Theme surprise</h2>
|
||||||
</div>
|
</div>
|
||||||
<span class="confidence-tag">{{ positiveObservations }}/{{ observationRows.length }} positive</span>
|
<span class="confidence-tag">{{ positiveObservations }}/{{ observationRows.length }} เป็นบวก</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="pulse-lead">
|
<div class="pulse-lead">
|
||||||
<span class="pulse-number">{{ formatNumber(summary.theme_surprise) }}σ</span>
|
<span class="pulse-number">{{ formatNumber(summary.theme_surprise) }}σ</span>
|
||||||
<span class="pulse-copy">The tourism basket is running {{ summary.theme_surprise >= 0 ? 'above' : 'below' }} its seasonal expectation. The score is an input, not an order.</span>
|
<span class="pulse-copy">ธีม tourism วิ่ง{{ summary.theme_surprise >= 0 ? 'สูงกว่า' : 'ต่ำกว่า' }}ค่าตามฤดูกาล — เป็น input ไม่ใช่คำสั่งซื้อขาย.</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="observation-list">
|
<div class="observation-list">
|
||||||
<div v-for="observation in observationRows" :key="observation.metric_key" class="observation-row">
|
<div v-for="observation in observationRows" :key="observation.metric_key" class="observation-row">
|
||||||
@@ -479,36 +480,36 @@ onMounted(loadDashboard)
|
|||||||
<article class="panel lineage-panel" id="lineage">
|
<article class="panel lineage-panel" id="lineage">
|
||||||
<div class="panel-header">
|
<div class="panel-header">
|
||||||
<div>
|
<div>
|
||||||
<div class="section-kicker">02 / Provenance</div>
|
<div class="section-kicker">02 / ที่มาของข้อมูล</div>
|
||||||
<h2>Can we trust the input?</h2>
|
<h2>เชื่อถือข้อมูลได้ไหม?</h2>
|
||||||
</div>
|
</div>
|
||||||
<span class="status-tag" :class="summary.data_health.status === 'high' ? '' : 'warning-tag'">{{ displayLabel(summary.data_health.status) }}</span>
|
<span class="status-tag" :class="summary.data_health.status === 'high' ? '' : 'warning-tag'">{{ displayLabel(summary.data_health.status) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="lineage-list">
|
<div class="lineage-list">
|
||||||
<div class="lineage-item"><span>Source</span><strong>{{ summary.data_health.source_id }}</strong></div>
|
<div class="lineage-item"><span>แหล่งข้อมูล</span><strong>{{ summary.data_health.source_id }}</strong></div>
|
||||||
<div class="lineage-item"><span>Published</span><strong>{{ formatDate(summary.data_health.published_at) }}</strong></div>
|
<div class="lineage-item"><span>เผยแพร่</span><strong>{{ formatDate(summary.data_health.published_at) }}</strong></div>
|
||||||
<div class="lineage-item"><span>Retrieved</span><strong>{{ formatDate(summary.data_health.retrieved_at) }}</strong></div>
|
<div class="lineage-item"><span>ดึงข้อมูล</span><strong>{{ formatDate(summary.data_health.retrieved_at) }}</strong></div>
|
||||||
<div class="lineage-item"><span>Vintage</span><strong>{{ summary.data_health.vintage_id }}</strong></div>
|
<div class="lineage-item"><span>Vintage</span><strong>{{ summary.data_health.vintage_id }}</strong></div>
|
||||||
<div class="lineage-item"><span>History</span><strong>{{ summary.data_health.history_points || '—' }} points</strong></div>
|
<div class="lineage-item"><span>ประวัติ</span><strong>{{ summary.data_health.history_points || '—' }} points</strong></div>
|
||||||
<div class="lineage-item"><span>Parser</span><strong>{{ summary.data_health.parser_version || '—' }}</strong></div>
|
<div class="lineage-item"><span>Parser</span><strong>{{ summary.data_health.parser_version || '—' }}</strong></div>
|
||||||
<div class="lineage-item"><span>Replay</span><strong>{{ summary.data_health.replayable ? 'Available' : 'Not captured' }}</strong></div>
|
<div class="lineage-item"><span>Replay</span><strong>{{ summary.data_health.replayable ? 'มี' : 'ไม่มี' }}</strong></div>
|
||||||
<div class="lineage-item"><span>Raw hash</span><strong>{{ summary.data_health.raw_payload_hash ? summary.data_health.raw_payload_hash.slice(0, 16) : '—' }}</strong></div>
|
<div class="lineage-item"><span>Raw hash</span><strong>{{ summary.data_health.raw_payload_hash ? summary.data_health.raw_payload_hash.slice(0, 16) : '—' }}</strong></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="lineage-note">Every signal will carry its source, release timestamp and vintage. Revised data never silently rewrites the past.</div>
|
<div class="lineage-note">ทุกสัญญาณพกที่มา, เวลาเผยแพร่ และ vintage ของมัน ข้อมูลที่ถูกแก้ภายหลังไม่เคยเขียนทับอดีตแบบเงียบ.</div>
|
||||||
</article>
|
</article>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="panel signal-panel" id="signals">
|
<section class="panel signal-panel" id="signals">
|
||||||
<div class="panel-header signal-header">
|
<div class="panel-header signal-header">
|
||||||
<div>
|
<div>
|
||||||
<div class="section-kicker">03 / Deterministic output</div>
|
<div class="section-kicker">03 / ผลลัพธ์เชิงกำหนด</div>
|
||||||
<h2>Signal board</h2>
|
<h2>ตารางสัญญาณ</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="strategy-meta">{{ signalData.strategy_version }} <span>·</span> target weights, not orders</div>
|
<div class="strategy-meta">{{ signalData.strategy_version }} <span>·</span> target weights ไม่ใช่คำสั่งซื้อขาย</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="table-wrap">
|
<div class="table-wrap">
|
||||||
<table>
|
<table>
|
||||||
<thead><tr><th>Rank</th><th>Symbol</th><th>Side</th><th>Score</th><th>Exposure</th><th>Reason</th><th>Target</th><th></th></tr></thead>
|
<thead><tr><th>อันดับ</th><th>หุ้น</th><th>ทิศทาง</th><th>Score</th><th>Exposure</th><th>เหตุผล</th><th>Target</th><th></th></tr></thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="signal in signals" :key="signal.symbol">
|
<tr v-for="signal in signals" :key="signal.symbol">
|
||||||
<td class="muted-cell">{{ String(signal.rank).padStart(2, '0') }}</td>
|
<td class="muted-cell">{{ String(signal.rank).padStart(2, '0') }}</td>
|
||||||
@@ -528,10 +529,10 @@ onMounted(loadDashboard)
|
|||||||
<section class="panel research-panel" id="research-run">
|
<section class="panel research-panel" id="research-run">
|
||||||
<div class="panel-header">
|
<div class="panel-header">
|
||||||
<div>
|
<div>
|
||||||
<div class="section-kicker">04 / Frozen research run</div>
|
<div class="section-kicker">04 / Research run</div>
|
||||||
<h2>Can the evidence support a descriptive study?</h2>
|
<h2>หลักฐานพอจะรองรับ study เชิงพรรณนาไหม?</h2>
|
||||||
</div>
|
</div>
|
||||||
<button class="primary-button" :disabled="researchRunning" @click="runResearch">{{ researchRunning ? 'Running…' : 'Run exploratory study' }}</button>
|
<button class="primary-button" :disabled="researchRunning" @click="runResearch">{{ researchRunning ? 'กำลังรัน…' : 'รัน exploratory study' }}</button>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="researchRun" class="research-grid">
|
<div v-if="researchRun" class="research-grid">
|
||||||
<div>
|
<div>
|
||||||
@@ -541,7 +542,7 @@ onMounted(loadDashboard)
|
|||||||
</div>
|
</div>
|
||||||
<div class="gate-list">
|
<div class="gate-list">
|
||||||
<div class="gate-row"><span>Independent vintages</span><strong>{{ researchRun.gates.vintages.available_events }} / {{ researchRun.gates.vintages.required_events }} · {{ displayLabel(researchRun.gates.vintages.status) }}</strong></div>
|
<div class="gate-row"><span>Independent vintages</span><strong>{{ researchRun.gates.vintages.available_events }} / {{ researchRun.gates.vintages.required_events }} · {{ displayLabel(researchRun.gates.vintages.status) }}</strong></div>
|
||||||
<div class="gate-row"><span>Price source</span><strong>{{ displayLabel(researchRun.gates.prices.quality) || 'missing' }} · {{ displayLabel(researchRun.gates.prices.status) }}</strong></div>
|
<div class="gate-row"><span>ราคา</span><strong>{{ displayLabel(researchRun.gates.prices.quality) || 'ไม่มี' }} · {{ displayLabel(researchRun.gates.prices.status) }}</strong></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="researchRun?.result?.windows" class="research-results">
|
<div v-if="researchRun?.result?.windows" class="research-results">
|
||||||
@@ -551,36 +552,36 @@ onMounted(loadDashboard)
|
|||||||
<span>{{ (windowResult.hit_rate * 100).toFixed(0) }}% hit · {{ windowResult.event_count }} events</span>
|
<span>{{ (windowResult.hit_rate * 100).toFixed(0) }}% hit · {{ windowResult.event_count }} events</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!researchRun" class="empty-research">No frozen research run yet. Run the check to persist the current inputs and gate decision.</div>
|
<div v-if="!researchRun" class="empty-research">ยังไม่มี frozen research run. รันการตรวจเพื่อเก็บ input กับ gate decision ปัจจุบัน.</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="bottom-grid">
|
<section class="bottom-grid">
|
||||||
<article class="panel thesis-panel">
|
<article class="panel thesis-panel">
|
||||||
<div class="section-kicker">05 / Research note</div>
|
<div class="section-kicker">05 / บันทึกการวิเคราะห์</div>
|
||||||
<h2>Read the signal as a thesis.</h2>
|
<h2>อ่านสัญญาณเป็นสมมติฐาน.</h2>
|
||||||
<p>Tourism observations are {{ summary.theme_surprise >= 0 ? 'above' : 'below' }} expectation, so exposure determines which names receive positive or negative scores. The system deliberately stops before execution: a human still needs to review valuation, price-in, liquidity and risk.</p>
|
<p>ข้อมูล tourism {{ summary.theme_surprise >= 0 ? 'สูงกว่า' : 'ต่ำกว่า' }}ค่าที่คาด เป็นตัวกำหนดว่าหุ้นตัวไหนได้ score บวกหรือลบ ระบบหยุดก่อนสั่ง execution — ยังต้องมีคน review valuation, price-in, liquidity และ risk.</p>
|
||||||
<div class="thesis-rule"><span></span>Surprise × Exposure × Confidence</div>
|
<div class="thesis-rule"><span></span>Surprise × Exposure × Confidence</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="panel ledger-panel">
|
<article class="panel ledger-panel">
|
||||||
<div class="panel-header">
|
<div class="panel-header">
|
||||||
<div><div class="section-kicker">06 / Simulation</div><h2>Paper ledger</h2></div>
|
<div><div class="section-kicker">06 / Simulation</div><h2>Paper ledger</h2></div>
|
||||||
<span class="status-tag neutral-tag">Internal only</span>
|
<span class="status-tag neutral-tag">ภายในเท่านั้น</span>
|
||||||
</div>
|
</div>
|
||||||
<p>Record an assumed fill to test portfolio behavior. This does not send a webhook or order.</p>
|
<p>บันทึก assumed fill เพื่อทดสอบพฤติกรรม portfolio — ไม่ส่ง webhook หรือ order.</p>
|
||||||
<div v-if="paperAuthWarning" class="paper-auth-warning">{{ paperAuthWarning }}</div>
|
<div v-if="paperAuthWarning" class="paper-auth-warning">{{ paperAuthWarning }}</div>
|
||||||
<div v-if="notice" class="notice" :class="notice.includes('recorded') || notice.includes('unlocked') ? 'notice-success' : 'notice-error'">{{ notice }}</div>
|
<div v-if="notice" class="notice" :class="notice.includes('recorded') || notice.includes('unlocked') ? 'notice-success' : 'notice-error'">{{ notice }}</div>
|
||||||
<div v-if="!paperAuthenticated && paperAuthMode === 'token' && paperAuthEnabled" class="auth-form">
|
<div v-if="!paperAuthenticated && paperAuthMode === 'token' && paperAuthEnabled" class="auth-form">
|
||||||
<div class="auth-copy">Paper writes are locked. Enter the local operator token; it is used only to create an HttpOnly session.</div>
|
<div class="auth-copy">การเขียน paper ถูกล็อก. กรอก local operator token (ใช้สร้าง HttpOnly session เท่านั้น).</div>
|
||||||
<input v-model="paperToken" type="password" autocomplete="current-password" placeholder="Paper-session token" aria-label="Paper-session token" />
|
<input v-model="paperToken" type="password" autocomplete="current-password" placeholder="Paper-session token" aria-label="Paper-session token" />
|
||||||
<button class="primary-button" :disabled="unlocking" @click="unlockPaper">{{ unlocking ? 'Unlocking…' : 'Unlock paper ledger' }}</button>
|
<button class="primary-button" :disabled="unlocking" @click="unlockPaper">{{ unlocking ? 'กำลังปลดล็อก…' : 'ปลดล็อก paper ledger' }}</button>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="!paperAuthenticated && paperAuthMode === 'token' && !paperAuthEnabled" class="empty-ledger">Paper writes are disabled by the server configuration.</div>
|
<div v-else-if="!paperAuthenticated && paperAuthMode === 'token' && !paperAuthEnabled" class="empty-ledger">Paper writes ถูกปิดโดย server configuration.</div>
|
||||||
<div v-else-if="paperAuthenticated && selectedSignal" class="entry-form">
|
<div v-else-if="paperAuthenticated && selectedSignal" class="entry-form">
|
||||||
<div class="selected-entry"><strong>{{ selectedSignal.symbol }}</strong><span>{{ selectedSignal.side }} · target {{ (selectedSignal.target_weight * 100).toFixed(1) }}%</span></div>
|
<div class="selected-entry"><strong>{{ selectedSignal.symbol }}</strong><span>{{ selectedSignal.side }} · target {{ (selectedSignal.target_weight * 100).toFixed(1) }}%</span></div>
|
||||||
<input v-model="assumedPrice" type="number" min="0.01" step="0.01" placeholder="Assumed fill price" aria-label="Assumed fill price" />
|
<input v-model="assumedPrice" type="number" min="0.01" step="0.01" placeholder="Assumed fill price" aria-label="Assumed fill price" />
|
||||||
<button class="primary-button" :disabled="submitting" @click="recordPaperEntry">{{ submitting ? 'Recording…' : 'Record paper entry' }}</button>
|
<button class="primary-button" :disabled="submitting" @click="recordPaperEntry">{{ submitting ? 'กำลังบันทึก…' : 'บันทึก paper entry' }}</button>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="empty-ledger">Choose a signal above to record a paper entry.</div>
|
<div v-else class="empty-ledger">เลือกสัญญาณข้างบนเพื่อบันทึก paper entry.</div>
|
||||||
</article>
|
</article>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Reference in New Issue
Block a user