code wiki / (root) / nx_fin_marketdata.nx

nx_fin_marketdata.nx source

↩ module page · 110 lines · 5539 B

1// nx_fin_marketdata.nx -- R1 of the trading stack: the PRICE + LIQUIDITY sensor's CORE. Pure, deterministic 2// OHLCV parsing + the ADV / capacity math that answers the SCALING WALL ("can a small stake grow to $1M in 3// this name?"). Source-agnostic: parses a standard OHLCV CSV (Date,Open,High,Low,Close,Volume by column) into 4// flat i64 bars [date,o,h,l,c,vol]; prices in CENTS, volume in SHARES, dollar-volume in CENTS (i64 fixed-point, 5// NO floats -- money is exact by construction). The LIVE fetch is a separate adapter (nx_https_fetch_follow); 6// some hosts still need TLS-robustness work (stooq/cornell/FRED fail the 1.3 handshake = err -3), so the PARSER 7// is proven here on KNOWN bytes, independent of any one source -- and R2's backtester consumes these same bars. 8// license_tier: ORIGINAL 9import "nx_syscalls.nx" 10 11const MD_FIELDS: i64 = 6 // per bar: [date(YYYYMMDD), open, high, low, close, volume] 12 13// parse an unsigned integer from s[off..off+len) (stops at first non-digit) 14func md_atoi(s: *u8, off: i64, len: i64) -> i64 { 15 var i: i64 = off; let end: i64 = off + len; var v: i64 = 0 16 while i < end { 17 let c: i64 = s[i] as i64 18 if c < 48 { return v } 19 if c > 57 { return v } 20 v = v*10 + (c - 48); i = i + 1 21 } 22 return v 23} 24 25// parse a decimal price string to CENTS (scale 100): "185.64"->18564, "9.8"->980, "12"->1200 26func md_price_cents(s: *u8, off: i64, len: i64) -> i64 { 27 var i: i64 = off; let end: i64 = off + len 28 var whole: i64 = 0; var frac: i64 = 0; var fdig: i64 = 0; var dot: i64 = 0; var neg: i64 = 0 29 if i < end { if (s[i] as i64) == 45 { neg = 1; i = i + 1 } } // '-' 30 while i < end { 31 let c: i64 = s[i] as i64 32 if c == 46 { dot = 1; i = i + 1 } // '.' 33 else { 34 if c < 48 { i = end } 35 else { 36 if c > 57 { i = end } 37 else { 38 let d: i64 = c - 48 39 if dot == 0 { whole = whole*10 + d } else { if fdig < 2 { frac = frac*10 + d; fdig = fdig + 1 } } 40 i = i + 1 41 } 42 } 43 } 44 } 45 while fdig < 2 { frac = frac*10; fdig = fdig + 1 } 46 var cents: i64 = whole*100 + frac 47 if neg == 1 { cents = 0 - cents } 48 return cents 49} 50 51// parse "YYYY-MM-DD" (any non-digit ignored) to an integer key YYYYMMDD 52func md_date_key(s: *u8, off: i64, len: i64) -> i64 { 53 var i: i64 = off; let end: i64 = off + len; var v: i64 = 0 54 while i < end { let c: i64 = s[i] as i64; if c >= 48 { if c <= 57 { v = v*10 + (c - 48) } } i = i + 1 } 55 return v 56} 57 58// index of first byte `ch` in s[from..end), else end 59func md_find(s: *u8, from: i64, end: i64, ch: i64) -> i64 { var i: i64 = from; while i < end { if (s[i] as i64) == ch { return i } i = i + 1 } return end } 60 61// parse a standard OHLCV CSV in buf[0..n) into flat i64 bars (MD_FIELDS each). A header line (first byte not a 62// digit) is skipped. Column order Date,Open,High,Low,Close,Volume. Returns bar count (<= max_bars). 63func md_parse_csv(buf: *u8, n: i64, out: *i64, max_bars: i64) -> i64 { 64 var ls: i64 = 0; var bars: i64 = 0 65 while ls < n { 66 if bars >= max_bars { return bars } 67 let le: i64 = md_find(buf, ls, n, 10) // end of line ('\n' or n) 68 var lend: i64 = le 69 if lend > ls { if (buf[lend-1] as i64) == 13 { lend = lend - 1 } } // strip trailing '\r' 70 if lend > ls { 71 let c0: i64 = buf[ls] as i64 72 if c0 >= 48 { if c0 <= 57 { // data row (starts with a digit) 73 var fs: i64 = ls; var col: i64 = 0 74 while col < MD_FIELDS { 75 let fe: i64 = md_find(buf, fs, lend, 44) // next ',' 76 let flen: i64 = fe - fs 77 if col == 0 { out[bars*MD_FIELDS+0] = md_date_key(buf, fs, flen) } 78 if col == 1 { out[bars*MD_FIELDS+1] = md_price_cents(buf, fs, flen) } 79 if col == 2 { out[bars*MD_FIELDS+2] = md_price_cents(buf, fs, flen) } 80 if col == 3 { out[bars*MD_FIELDS+3] = md_price_cents(buf, fs, flen) } 81 if col == 4 { out[bars*MD_FIELDS+4] = md_price_cents(buf, fs, flen) } 82 if col == 5 { out[bars*MD_FIELDS+5] = md_atoi(buf, fs, flen) } 83 fs = fe + 1 84 col = col + 1 85 if fe >= lend { col = MD_FIELDS } // no more fields on this line 86 } 87 bars = bars + 1 88 } } 89 } 90 if le >= n { return bars } 91 ls = le + 1 92 } 93 return bars 94} 95 96// close-price dollar-volume of one bar, in CENTS (close_cents * volume) 97func md_dollar_vol(bars: *i64, idx: i64) -> i64 { return bars[idx*MD_FIELDS+4] * bars[idx*MD_FIELDS+5] } 98 99// average daily dollar-volume over the last `ndays` bars, in CENTS -- the LIQUIDITY signal 100func md_adv(bars: *i64, count: i64, ndays: i64) -> i64 { 101 var days: i64 = ndays; if days > count { days = count } 102 if days <= 0 { return 0 } 103 var i: i64 = count - days; var sum: i64 = 0 104 while i < count { sum = sum + bars[i*MD_FIELDS+4] * bars[i*MD_FIELDS+5]; i = i + 1 } 105 return sum / days 106} 107 108// max $ (cents) deployable per day in a name at `participation_bps` of ADV (the SCALING-WALL cap). 109// participation_bps=100 => 1% of ADV; 10000 bps => 100% (whole ADV). 110func md_capacity(adv_cents: i64, participation_bps: i64) -> i64 { return (adv_cents * participation_bps) / 10000 }