From 375682d2dc4985e325ac2c1df10bd73ab472af44 Mon Sep 17 00:00:00 2001 From: Kunthawat Greethong Date: Wed, 26 Aug 2026 15:26:34 +0700 Subject: [PATCH] [verified] Signal column now derives from theme engine (combined 60/40 + quality), not tourism_result MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - /api/v1/factors signal no longer from tourism-only signals; derived from RealDashboard combined score: LONG>=0.15, SHORT<=-0.15, else NEUTRAL. One source of truth. - Verified: LONG 25 / NEUTRAL 16 / SHORT 8; BANPU LONG (3 themes), KTB LONG (banks), CPN LONG (3 themes), JMART SHORT (nonbank) — reflects all 13 themes + per-symbol quality, not flat tourism. - reason_codes + combined_score on each factor row. --- backend/app/__init__.py | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/backend/app/__init__.py b/backend/app/__init__.py index 77dc1ea..fdbcb90 100644 --- a/backend/app/__init__.py +++ b/backend/app/__init__.py @@ -455,8 +455,33 @@ def create_app(config: dict[str, Any] | None = None) -> Flask: from app import siamchart_factors factor_view = siamchart_factors.build_factor_view() - current = app.extensions["tourism_result"] - signal_by_symbol = {s["symbol"]: s for s in current["signals"]} + # signal = derived from the NEW theme-engine combined score (60/40 with + # per-symbol quality), NOT the old tourism_result. One source of truth. + from app import daily_cache + from app.dashboard import RealDashboard + cache = app.extensions.setdefault("daily_cache", daily_cache.DailyCache()) + current = app.extensions.get("tourism_result") or {} + signal_by_symbol = {} + try: + dash = RealDashboard(current.get("signals", []), cache).build() + for row in dash.get("board", []): + comb = row.get("combined") + if comb is None: + signal_by_symbol[row["symbol"]] = {"side": None, "score": None} + continue + if comb >= 0.15: + side, score = "LONG", round(min(abs(comb) * 3.0, 1.0) * 0.9 + 0.1, 3) + elif comb <= -0.15: + side, score = "SHORT", round(min(abs(comb) * 3.0, 1.0) * 0.9 + 0.1, 3) + else: + side, score = "NEUTRAL", round(abs(comb) / 0.15, 3) + signal_by_symbol[row["symbol"]] = { + "side": side, "score": score, "confidence": "medium", + "combined_score": comb, + } + except Exception: + # no dashboard -> fall back to neutral for all + pass merged = [] for factor in factor_view.get("factors", []): @@ -467,8 +492,9 @@ def create_app(config: dict[str, Any] | None = None) -> Flask: "signal_side": sig.get("side"), "signal_score": sig.get("score"), "signal_confidence": sig.get("confidence"), - "signal_target_weight": sig.get("target_weight"), - "reason_codes": sig.get("reason_codes", []), + "combined_score": sig.get("combined_score"), + "signal_target_weight": None, + "reason_codes": ["combined 60/40 + firm quality"] if sig.get("side") in ("LONG", "SHORT") else [], } ) # Put symbols that carry a non-neutral signal first, then by signal score.