8.9 KiB
Multi-Theme + Simulation Implementation Plan — REVISION 2
For Hermes: Execute task-by-task with
todotracking and per-task commits. TDD for all logic; visual verification for UI/CSS.
Goal: Rebuild the single-theme tourism demo into a multi-theme research platform with (a) a combined dashboard (all themes + stock recommendation table), (b) per-theme detail pages, (c) a capital allocation simulation (same engine used for both backtest and forward test), and (d) an MT5 order interface (forward test sends dry-run orders; live gate kept off in this round). UI in Thai (technical terms stay English). Uses real data + daily cache, not mocks.
Themes (3 alternative factors):
tourism— Tourism Pulse (signal exists, real BOT data path)auto_credit— Auto Credit Cycle (registry slot; data pipeline needs a real source before it can score)refining_energy— Refining / Energy Spread (registry slot; data pipeline TBD)
Siamchart is treated as a fundamental data provider, not a theme. It supplies PE/EPS/Yield/P/BV/ROE per symbol and contributes to the combined score with a lower weight.
Confirmed requirements (from user, 2026-08-25)
- 3 themes = 3 alternative factors (tourism, auto_credit, refining_energy). A symbol can appear in multiple themes.
- Score combining: each symbol has a theme score (average across all themes that score it) and a siamchart score. Combined = theme_score × 60% + siamchart_score × 40%.
- 8-symbol live data is OK for testing logic; production must cover the full universe (collectors must be scalable, not hard-coded to 8).
- "ไม่จ่ายปันผล" (bucket 2) = a high-score stock that does not pay a dividend (is_dividend = False / yield ≤ 0).
- Paper ledger is replaced by a forward test that reuses the same simulation engine as backtest — the only difference is real MT5 order dispatch. We build the MT5 order interface; live dispatch remains gated off (forward/dry-run in this round).
- Data quality / provenance = real sources, cached daily — not fixture mocks. Provenance shows where each alternative factor comes from.
- Signal board + Stock board = ONE table, signal-focused.
- UI language = Thai (technical terms English).
- Remove unused/unnecessary dashboard sections.
PART A — Multi-Theme Backend
Task A1: Theme registry
Files: create backend/app/themes.py; create backend/tests/test_themes.py
Registry with 3 theme slots + their factor provider. list_themes() returns them. Registry is pluggable so a theme can be enabled=False while its data pipeline is absent.
Task A2: Combined signal aggregation
Files: modify backend/app/themes.py, backend/app/__init__.py; tests
/api/v1/dashboard returns:
- Per-symbol
theme_score= mean of enabled themes that score it siamchart_score(normalized)combined_score = 0.6 * theme_score + 0.4 * siamchart_scoreside(LONG/SHORT/NEUTRAL) from combined- per-theme contributions
Normalization: each provider's raw score is z-scored (or min-max over the live universe) before combining, so weights are meaningful across heterogeneous scales.
Task A3: Per-theme detail endpoint
/api/v1/themes/<id> — 404 unknown; returns that theme's observations/surprise/signals/lineage. Also exposes /api/v1/themes (registry incl. enabled status + data source).
Task A4: Daily cache layer (real data, no mocks)
Files: create backend/app/daily_cache.py; tests
Fetch-once-then-cache daily:
- Keyed by
(source, as_of). - TTL ~24h; stale-if-error fallback to last good cache.
- Ensures tourism + siamchart reads hit cache instead of re-fetching every dashboard load, and sets up the "update daily" cadence the user wants.
PART B — Simulation / Backtest / Forward-Test Engine
Task B1: Price-series loader
Files: create backend/app/simulation.py; tests
Load Yahoo price snapshot series[SYM].bars → {date: adjusted_close} for all symbols. Covers 2024-01-03 → 2026-08-24.
Task B2: Capital allocation core (allocate_capital)
Files: backend/app/simulation.py + tests
Given capital, per-symbol combined_score, is_dividend, dividend_yield, latest price:
- Bucket 1 (50%) — highest combined_score among dividend-paying names.
- Bucket 2 (20%) — highest combined_score among non-dividend names.
- Bucket 3 (30%) — highest dividend yield among names not already bought in bucket 1 (ignores score).
Per symbol: minimum 100 shares. Rank by combined_score desc. If a bucket's first pick can't afford 100 shares, try progressively cheaper eligible names (that still pass the bucket's criteria). If no eligible name fits, leave remainder as cash.
- Allocation math:
qty = max(100, floor(available_cash / price / 100) * 100)within bucket cash. - Overflow flows bucket → bucket → cash.
Task B3: Simulation endpoint + forward-test mode
Files: backend/app/__init__.py; tests
POST /api/v1/simulation{capital}→ allocation plan (per-bucket orders, qty, notional, total, unallocated cash, data caveat: revised history not PIT).POST /api/v1/simulation/forward— same engine, but marks ordersexecution=paperand, if an MT5 bridge is configured, would dispatch. Live dispatch gated off (envMT5_ENABLE_ORDERmust be set AND explicit approval).
Task B4: MT5 order interface (interface only, live gated)
Files: create backend/app/mt5_bridge.py; tests (mock MetaTrader5 per mt5-data-service skill gotcha)
Abstract MT5Bridge with: connect, symbol_resolve, order_new (dry-run default), and a structural kill switch — no real dispatch path in code unless MT5_ENABLE_ORDER=1 AND explicit user approval. Follows the mt5-data-service / mql5-ea-development skill patterns (Windows-only MetaTrader5 stub for tests).
PART C — Frontend Multi-Theme UI (Thai, signal-first)
Task C1: Route shell
Files: frontend/src/App.vue, style.css
Hash-routing: #/ (dashboard), #/theme/:id, #/simulation. Sidebar nav in Thai: แดชบอร์ด, ธีม, การจำลอง (Simulation).
Task C2: Combined Dashboard (Thai)
- Theme strip: each active theme's surprise/label.
- ONE stock table (signal-first: combined score, side, theme contributions, then fundamentals PE/EPS/Yield/P/BV/ROE). Dividend filter + sortable columns kept. This replaces the separate Signal board + Stock board.
- Remove unused sections (Paper Ledger card, live receiver text, etc.).
Task C3: Theme detail page
#/theme/:id — that theme's observations (surprise bars), provenance (source used), and its signal contribution table.
Task C4: Simulation tab
#/simulation — Thai UI: enter capital, choose backtest/forward, render 3 buckets with orders; show unallocated cash and the "revised history, paper-only" caveat.
Task C5: Provenance (real data)
Dashboard provenance section reads from live cache metadata: source id, retrieved_at (daily cache), raw hash. No fixture labels.
Data requirements / open items (NOT guessed)
- auto_credit data source: needs a real public/paid source for vehicle sales, auto loan growth, auto NPL. Not yet identified.
auto_credittheme stays in registry asenabled=Falseuntil a source + collector exist. - refining_energy data source: crack/GRM spread or Thai refining/energy stats. Not yet identified.
enabled=Falseuntil built. - Full SET50 universe prices: Yahoo history currently covers 8 symbols; production simulation needs price history for the full universe. Requires extending the price collector to all symbols (scrape/SETSMART/MT5).
The three-theme registry is the interface; this round builds tourism + siamchart fully (real, cached) and stubs auto/energy until their data exists. Simulation works on whatever real symbols have both a price series and a score.
Verification
- Full backend suite (
-W error unittest discover) all pass (existing 140 + new). cd frontend && npm run buildsuccess.- Browser (Thai): dashboard lists themes (enabled + disabled), combined signal-first table with dividend filter + sort, simulation returns allocation, per-theme detail works.
git diff --check, static scan clean, independent fail-closed code review.- Per-task commits.
Closed/open per user's 10 points
| # | User requirement | Status in plan |
|---|---|---|
| 1 | 3 themes (tourism, auto, energy) | Registry + tourism live; auto/energy enabled=False pending data source |
| 2 | combined = 60% theme + 40% siamchart | A2 |
| 3 | 8-symbol OK for logic, prod full | Simulation scales to any symbol with price+score |
| 4 | bucket2 = high score, no dividend | B2 |
| 5 | paper→forward test = same engine; MT5 iface | B3, B4 |
| 6 | real data + daily cache, provenance real | A4, C5 |
| 7 | one signal-first table | C2 |
| 8 | Thai UI | C1–C4 |
| 9 | remove unused | C2 |
| 10 | (mt5 scope) interface built, live gated | B4 |