feat(scheduler): daily Siamchart SET50 board collection (writes master + vintage, feeds dashboard)

This commit is contained in:
Kunthawat Greethong
2026-08-28 20:28:25 +07:00
parent ffad4dfd8d
commit b1c8a91ed6

View File

@@ -53,7 +53,7 @@ _FREQ_SECONDS: dict[str, int] = {
# Data that legitimately changes every day (or faster) is refreshed separately
# at a daily cadence rather than on the slow factor loop.
_DAILY_JOBS: List[dict] = [
{"key": "siamchart_vintages", "label": "Siamchart SET50 snapshot (vintage)", "fn": "_record_siamchart_vintages", "frequency": "daily"},
{"key": "siamchart_vintages", "label": "Siamchart SET50 snapshot (vintage)", "fn": "_collect_siamchart_daily", "frequency": "daily"},
{"key": "price_snapshot", "label": "ราคาหุ้น SET50 (Yahoo)", "fn": "_refresh_price_snapshot", "frequency": "daily"},
]
@@ -279,6 +279,62 @@ class AppDataScheduler:
except Exception: # noqa: BLE001
log.exception("set50 siamchart vintage persist failed (non-fatal)")
def _collect_siamchart_daily(self) -> None:
"""Daily: fetch the Siamchart SET50 fundamental board, write the master
snapshot, and persist a vintage (so dashboard + strict backtest are fed).
This is what actually puts stocks on the dashboard page: on a fresh
deploy the volume starts empty (no ``set50_master.json``), so the board
stays blank until the first successful daily collection. We fetch the
group table (``fetch_financial``) then each symbol's stock-info page
for ratios/income (``fetch_stock_info``), write ``set50_master.json``
(the exact shape ``_load_siamchart_snapshot``/``siamchart_factors``
consume), and finally record a vintage.
Failures are logged to the source-health panel via the caller's
exception handling; on network error the previous master is retained.
"""
import datetime as _dt
import json
from . import siamchart
from .siamchart_vintages import SiamchartVintageStore, SiamchartVintageError
rows = siamchart.fetch_financial("SET50")
details: dict = {}
for row in rows:
try:
info = siamchart.fetch_stock_info(row.symbol)
details[row.symbol] = info.to_dict()
except siamchart.SiamchartError as exc:
log.warning("set50 stock-info %s failed: %s", row.symbol, exc)
details[row.symbol] = {"error": str(exc)}
now_utc = _dt.datetime.now(_dt.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
payload = {
"source": "siamchart",
"retrieved_at": now_utc,
"mode": "group",
"group": "SET50",
"url": siamchart.build_url("SET50"),
"count": len(rows),
"rows": [row.to_dict() for row in rows],
"details": details,
"details_count": len(details),
}
out = self.data_root / "siamchart" / "set50_master.json"
out.parent.mkdir(parents=True, exist_ok=True)
out.write_text(json.dumps(payload, ensure_ascii=False, sort_keys=True, indent=2),
encoding="utf-8")
log.info("set50 siamchart board collected (%d rows, %d details)",
len(rows), len(details))
# persist a vintage so the strict backtest fundamental is PIT
try:
store = SiamchartVintageStore(self.data_root)
store.persist(payload)
except SiamchartVintageError as exc:
log.warning("set50 siamchart vintage persist failed after collect: %s", exc)
def _maybe_refresh_dated_dividends(self) -> None:
"""Cooldown-gated fetch of real dated dividend history into the ledger.