(a) R1-R5 (factor-refinement, grounded in methodology-research.md): - R1 (PEAD): EPS-growth weight raised 1.0->1.5 in build_siamchart_score / symbol_breakdown (Bernard-Thomas 1990, Livnat-Mendenhall 2006) - R2 (momentum): 12-1 momentum factor from Yahoo price snapshot (Jegadeesh-Titman 93; lite weight 0.5) - R3 (regime): binary bear gate -> continuous stress = negative-themes fraction, smooth LONG/SHORT shift - R5 (dividend screen): non-dividend / cut-yield names no longer go LONG (screen-off) - R4 (earnings-revision) deferred: no free EPS-forecast source yet (documented) (b) bank-sector NPL collector (BOT reportID 794, financial&insurance sector): - refactored auto_npl to expose shared _parse_sector; new bank_npl.py reuses it - registered bank_npl FACTOR -> auto-appears in sources table (6 rows) + blends into banks theme surprise (real NPL) - +unit tests (test_bank_npl), test_dashboard updated (6 sources) 205 tests pass; verified live API (banks surprise incl. NPL 1.07, 6 sources).
55 lines
2.0 KiB
Python
55 lines
2.0 KiB
Python
"""Bank-sector NPL / credit-quality factor — BOT Gross NPLs by business type.
|
|
|
|
Source: https://app.bot.or.th/BTWS_STAT/statistics/ReportPage.aspx?reportID=794
|
|
(Bank of Thailand, FI_NP_003_S2). This is the SAME report the auto theme already
|
|
consumes; the bank variant selects the **financial & insurance** sector row —
|
|
the closest public proxy for commercial-bank credit quality.
|
|
|
|
Higher NPL / higher % of loans is bearish for bank earnings (provisioning drag),
|
|
so the factor sign is -1 at the registry level.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from typing import Optional
|
|
|
|
from .auto_npl import _fetch, _parse_sector, AutoNplError
|
|
|
|
# Sector label on the BOT 794 page that maps to financial/banking credit quality.
|
|
# ('กิจกรรมทางการเงินและการประกันภัย' = financial & insurance activities)
|
|
BANK_SECTOR_LABEL = "กิจกรรมทางการเงินและการประกันภัย"
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class BankNplSnapshot:
|
|
npl_amount: Optional[float] = None
|
|
pct_of_npls: Optional[float] = None
|
|
pct_of_loans: Optional[float] = None
|
|
period: str = ""
|
|
source: str = "bot"
|
|
|
|
def to_dict(self) -> dict:
|
|
return {
|
|
"source": self.source,
|
|
"period": self.period,
|
|
"npl_amount": self.npl_amount,
|
|
"pct_of_npls": self.pct_of_npls,
|
|
"pct_of_loans": self.pct_of_loans,
|
|
}
|
|
|
|
|
|
def parse_bank_npl_html(html_text: str, label: str = BANK_SECTOR_LABEL) -> BankNplSnapshot:
|
|
"""Parse the BOT NPL table; return the financial-sector row (latest period)."""
|
|
row = _parse_sector(html_text, label)
|
|
if row is None:
|
|
raise AutoNplError(f"no {label!r} NPL row found in BOT NPL page")
|
|
return BankNplSnapshot(
|
|
npl_amount=row[0], pct_of_npls=row[1], pct_of_loans=row[2], period=row[3],
|
|
)
|
|
|
|
|
|
def fetch_bank_npl(timeout: float = 30.0) -> BankNplSnapshot:
|
|
from .auto_npl import _URL
|
|
return parse_bank_npl_html(_fetch(_URL, timeout=timeout))
|