code wiki / (root) / nx_piotroski_lib.nx

nx_piotroski_lib.nx source

↩ module page · 64 lines · 3438 B

1// nx_piotroski_lib.nx -- F-SCORE: Piotroski 9-point fundamental-strength score, integer-exact. 2// 3// Pairs with the Altman-Z distress engine (nx_fin_zscore): Altman asks "is this firm in trouble?", the 4// Piotroski F-score asks "is this firm's fundamental picture STRENGTHENING?" -- nine binary year-over-year 5// tests across profitability, leverage/liquidity, and operating efficiency, each worth one point, summed 6// 0..9. High (8-9) = strong improving fundamentals; low (0-2) = deteriorating. 7// 8// Every test is an integer comparison -- no float, so the score is reproducible to the point. Inputs are 9// scaled integers (ratios in basis points, money in minor units); a real pipeline gets them from 10// nx_xbrl-parsed filings. 11// 12// ★FAIL-CLOSED + COMPOSES nx_xbrl: the missing-input sentinel PIO_NA is the SAME value nx_xbrl returns 13// for an absent fact (XB_NO_FACT = -999999999). So if any required metric could not be parsed from the 14// filing, pio_fscore returns PIO_INCOMPLETE rather than scoring an unverifiable firm -- a partial F-score 15// read as a real one is exactly the kind of false signal that gets money committed on bad data. 16// 17// DRY: composes nx_matter_lib helpers. license_tier: ORIGINAL No hw writes (Rule 26). LIB. 18 19import "nx_matter_lib.nx" 20 21const PIO_NA: i64 = 0 - 999999999 // missing input (matches nx_xbrl XB_NO_FACT) -> incomplete 22const PIO_INCOMPLETE: i64 = 0 - 1 // a required metric was absent; score not computable 23const PIO_N: i64 = 14 // number of input metrics 24 25// metric indices into the m[] array (a declared contract): 26// 0 net_income 1 roa_bp 2 roa_bp_prior 3 cfo (operating cash flow) 27// 4 ltd_ratio_bp 5 ltd_ratio_bp_prior 6 current_ratio_bp 7 current_ratio_bp_prior 28// 8 shares 9 shares_prior 10 gross_margin_bp 11 gross_margin_bp_prior 29// 12 asset_turnover_bp 13 asset_turnover_bp_prior 30 31// ★the F-score (0..9), or PIO_INCOMPLETE if any input is the missing sentinel. 32func pio_fscore(m: *i64) -> i64 { 33 var i: i64 = 0 34 while i < PIO_N { if m[i] == PIO_NA { return PIO_INCOMPLETE } i = i + 1 } 35 var s: i64 = 0 36 // -- profitability (4) -- 37 if m[1] > 0 { s = s + 1 } // T1 ROA positive 38 if m[3] > 0 { s = s + 1 } // T2 operating cash flow positive 39 if m[1] > m[2] { s = s + 1 } // T3 ROA improving year over year 40 if m[3] > m[0] { s = s + 1 } // T4 CFO > net income (earnings backed by cash, low accruals) 41 // -- leverage / liquidity / source of funds (3) -- 42 if m[4] < m[5] { s = s + 1 } // T5 long-term-debt ratio decreasing 43 if m[6] > m[7] { s = s + 1 } // T6 current ratio increasing (liquidity) 44 if m[8] <= m[9] { s = s + 1 } // T7 no share dilution 45 // -- operating efficiency (2) -- 46 if m[10] > m[11] { s = s + 1 } // T8 gross margin increasing 47 if m[12] > m[13] { s = s + 1 } // T9 asset turnover increasing 48 return s 49} 50 51// human grade for a score. INCOMPLETE is surfaced, never silently treated as WEAK. 52func pio_grade(score: i64) -> *u8 { 53 if score < 0 { return "INCOMPLETE" as *u8 } 54 if score >= 8 { return "STRONG" as *u8 } 55 if score >= 4 { return "MODERATE" as *u8 } 56 return "WEAK" as *u8 57} 58 59// 1 only if every input is present (the score stands on complete data). 60func pio_complete(m: *i64) -> i64 { 61 var i: i64 = 0 62 while i < PIO_N { if m[i] == PIO_NA { return 0 } i = i + 1 } 63 return 1 64}