feat(scheduler): daily Siamchart SET50 board collection (writes master + vintage, feeds dashboard)
This commit is contained in:
@@ -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.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user