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 }