nx_var_lib.nx source
↩ module page · 90 lines · 3955 B
1// nx_var_lib.nx -- F991 (risk core): VALUE-AT-RISK + EXPECTED SHORTFALL, integer-exact, fail-closed.
2//
3// A risk number computed from too few observations is worse than no number -- it looks authoritative and
4// is meaningless, and that is how blow-ups happen. So the sovereign property here is that the tail is
5// only reported when the data can support it: a 99% VaR needs at least ~100 observations to have a single
6// point in the tail, and asking for one from 10 data points REFUSES (fail-closed) rather than inventing a
7// quantile. Everything is integer minor units, no float -- a reproducible risk number an auditor can redo.
8//
9// Two measures:
10// Historical VaR -- the loss at the confidence quantile (the threshold a loss exceeds only (1-c) of the time)
11// Expected Shortfall (CVaR) -- the MEAN of the losses AT OR BEYOND the VaR quantile. This is the coherent,
12// sub-additive tail measure Basel III adopted over VaR; ES >= VaR always, and it sees
13// the shape of the tail VaR ignores. Reporting ES is the state of the art.
14//
15// Convention: losses[] are POSITIVE numbers (a loss of 500 = 500); larger = worse.
16// SCALE ENVELOPE (declared): var_sort is selection sort O(n^2) on a COPY (never mutates the caller's array);
17// a merge/quick sort is the rung for very long series. DRY: composes nx_matter_lib helpers. license_tier: ORIGINAL LIB.
18
19import "nx_matter_lib.nx"
20
21const VAR_BAD: i64 = 0 - 1 // invalid confidence (<=0 or >=100)
22const VAR_INSUFFICIENT: i64 = 0 - 2 // not enough observations for this confidence (fail-closed)
23
24// sort a[0..n) ascending, in place. selection sort (declared O(n^2)).
25func var_sort(a: *i64, n: i64) -> i64 {
26 var i: i64 = 0
27 while i < n {
28 var mn: i64 = i
29 var j: i64 = i + 1
30 while j < n {
31 if a[j] < a[mn] { mn = j }
32 j = j + 1
33 }
34 if mn != i {
35 let t: i64 = a[i]
36 a[i] = a[mn]
37 a[mn] = t
38 }
39 i = i + 1
40 }
41 return 0
42}
43
44// minimum observations to place at least one point in the (1-c) tail: ceil(100/(100-c)).
45func var_min_sample(confidence_pct: i64) -> i64 {
46 let d: i64 = 100 - confidence_pct
47 return (100 + d - 1) / d
48}
49
50// copy losses into a fresh sorted-ascending buffer; returns the buffer (or 0 on bad n).
51func var_sorted_copy(losses: *i64, n: i64) -> *i64 {
52 let s: *i64 = sys_mmap(8 * n) as *i64
53 var i: i64 = 0
54 while i < n { s[i] = losses[i]; i = i + 1 }
55 var_sort(s, n)
56 return s
57}
58
59// the quantile index for confidence c over n points.
60func var_index(n: i64, confidence_pct: i64) -> i64 {
61 var idx: i64 = (confidence_pct * n) / 100
62 if idx >= n { idx = n - 1 }
63 return idx
64}
65
66// ★HISTORICAL VaR at confidence c. Returns the tail-quantile loss, VAR_BAD on bad c,
67// VAR_INSUFFICIENT if n is too small for the confidence (fail-closed -- never invents a quantile).
68func var_historical(losses: *i64, n: i64, confidence_pct: i64) -> i64 {
69 if confidence_pct <= 0 { return VAR_BAD }
70 if confidence_pct >= 100 { return VAR_BAD }
71 if n < var_min_sample(confidence_pct) { return VAR_INSUFFICIENT }
72 let s: *i64 = var_sorted_copy(losses, n)
73 return s[var_index(n, confidence_pct)]
74}
75
76// ★EXPECTED SHORTFALL (CVaR): mean of the losses at or beyond the VaR quantile. Same fail-closed guards.
77// Integer mean (floor). ES >= VaR by construction.
78func var_expected_shortfall(losses: *i64, n: i64, confidence_pct: i64) -> i64 {
79 if confidence_pct <= 0 { return VAR_BAD }
80 if confidence_pct >= 100 { return VAR_BAD }
81 if n < var_min_sample(confidence_pct) { return VAR_INSUFFICIENT }
82 let s: *i64 = var_sorted_copy(losses, n)
83 let idx: i64 = var_index(n, confidence_pct)
84 var sum: i64 = 0
85 var cnt: i64 = 0
86 var i: i64 = idx
87 while i < n { sum = sum + s[i]; cnt = cnt + 1; i = i + 1 }
88 if cnt == 0 { return VAR_INSUFFICIENT }
89 return sum / cnt
90}