nx_debt_strategy.nx source
↩ module page · 112 lines · 4664 B
1// nx_debt_strategy.nx -- FIRST module of the comprehensive money system: a debt-payoff strategist. Given a set of
2// debts (balance, APR, minimum) and a monthly extra budget, it simulates AVALANCHE (attack highest APR -> minimises
3// interest) vs SNOWBALL (attack lowest balance -> fastest wins, motivation) month-by-month with real compounding,
4// and reports months-to-freedom + total interest for each. Integer-exact (cents / basis-points), deterministic, no
5// look-ahead. Budget-covers-minimums assumed; if a person cannot even meet minimums the sim returns months=-1
6// (UNDERWATER -- an honest hardship signal that routes to the relief modules). license_tier: ORIGINAL
7import "nx_syscalls.nx"
8const DS_MAGIC_120000: i64 = 120000
9
10const DS_FIELDS: i64 = 3 // per debt: [balance_cents, apr_bps, min_payment_cents]
11const DS_AVALANCHE: i64 = 0
12const DS_SNOWBALL: i64 = 1
13const DS_MAX_MONTHS: i64 = 6000 // 500-year guard -> underwater sentinel
14
15// one month's simple interest in cents: balance * (apr_bps/10000) / 12 = balance*apr_bps/120000.
16func ds_month_interest(balance: i64, apr_bps: i64) -> i64 {
17 if balance <= 0 { return 0 }
18 return balance * apr_bps / DS_MAGIC_120000
19}
20
21// index of the highest-APR debt still owed (avalanche target), or -1 if all paid.
22func ds_avalanche_target(bal: *i64, debts: *i64, n: i64) -> i64 {
23 var best: i64 = 0 - 1; var bestapr: i64 = 0 - 1; var i: i64 = 0
24 while i < n {
25 if bal[i] > 0 {
26 let apr: i64 = debts[i*DS_FIELDS+1]
27 if apr > bestapr { bestapr = apr; best = i }
28 }
29 i = i + 1
30 }
31 return best
32}
33
34// index of the smallest-balance debt still owed (snowball target), or -1 if all paid.
35func ds_snowball_target(bal: *i64, n: i64) -> i64 {
36 var best: i64 = 0 - 1; var bestbal: i64 = 0; var i: i64 = 0
37 while i < n {
38 if bal[i] > 0 {
39 if best == (0 - 1) { best = i; bestbal = bal[i] }
40 else { if bal[i] < bestbal { bestbal = bal[i]; best = i } }
41 }
42 i = i + 1
43 }
44 return best
45}
46
47// simulate payoff. strategy = DS_AVALANCHE|DS_SNOWBALL. out[0]=months (-1 = underwater), out[1]=total interest cents.
48func ds_simulate(debts: *i64, n: i64, extra: i64, strategy: i64, out: *i64) -> i64 {
49 let bal: *i64 = sys_mmap(8 * (n + 2)) as *i64
50 var budget: i64 = extra
51 var i: i64 = 0
52 while i < n { bal[i] = debts[i*DS_FIELDS+0]; budget = budget + debts[i*DS_FIELDS+2]; i = i + 1 }
53 var months: i64 = 0
54 var total_interest: i64 = 0
55 var run: i64 = 1
56 while run == 1 {
57 var owed: i64 = 0; var j: i64 = 0
58 while j < n { if bal[j] > 0 { owed = 1 } j = j + 1 }
59 if owed == 0 { run = 0 }
60 else {
61 if months >= DS_MAX_MONTHS { run = 0; months = 0 - 1 }
62 else {
63 var a: i64 = 0
64 while a < n {
65 if bal[a] > 0 {
66 let it: i64 = ds_month_interest(bal[a], debts[a*DS_FIELDS+1])
67 bal[a] = bal[a] + it; total_interest = total_interest + it
68 }
69 a = a + 1
70 }
71 var avail: i64 = budget
72 var m: i64 = 0
73 while m < n {
74 if bal[m] > 0 {
75 var p: i64 = debts[m*DS_FIELDS+2]
76 if p > bal[m] { p = bal[m] }
77 if p > avail { p = avail }
78 bal[m] = bal[m] - p; avail = avail - p
79 }
80 m = m + 1
81 }
82 var casc: i64 = 1
83 while casc == 1 {
84 if avail <= 0 { casc = 0 }
85 else {
86 var t: i64 = 0 - 1
87 if strategy == DS_SNOWBALL { t = ds_snowball_target(bal, n) }
88 else { t = ds_avalanche_target(bal, debts, n) }
89 if t < 0 { casc = 0 }
90 else {
91 var p2: i64 = avail
92 if p2 > bal[t] { p2 = bal[t] }
93 bal[t] = bal[t] - p2; avail = avail - p2
94 }
95 }
96 }
97 months = months + 1
98 }
99 }
100 }
101 out[0] = months
102 out[1] = total_interest
103 return months
104}
105
106// can the person even make the minimums after interest? 1 = yes, 0 = underwater (routes to relief modules).
107func ds_can_meet_minimums(debts: *i64, n: i64, extra: i64) -> i64 {
108 let out: *i64 = sys_mmap(16) as *i64
109 ds_simulate(debts, n, extra, DS_AVALANCHE, out)
110 if out[0] < 0 { return 0 }
111 return 1
112}