Application map / Investment allocation
How Investment Allocation Works
The reserve-fund allocator: what it recommends, why it is shaped the way it is, and where the running code deviates from this clean description.
The output is advisory. The allocator produces a recommended set of purchases — concrete CDs at named institutions, or treasuries — that a human executes. There is no trading, rebalancing, or brokerage integration. Soft constraints surface as warnings, never hard failures.
The problem
An HOA holds a reserve fund as idle cash. Idle cash earns nothing, so the excess above a safe operating minimum should be placed in low-risk fixed income — CDs and US treasuries — across a range of maturities. The allocation must satisfy four constraints simultaneously:
- Reserve floor. A minimum cash balance always remains uninvested (default $100,000).
- Liquidity. Money must be back in cash before it is needed: no allocation may lock funds past the date a net shortfall occurs, given known upcoming expenses and income.
- Insurance. Deposit products stay within FDIC limits so no dollar is exposed to bank failure.
- Purchasability. Recommendations map to real products at current yields, not abstract durations.
Because future expenses are estimates, the design is conservative by construction: overstated caution should cost a little yield, never solvency.
The pipeline
1 · Investable cash
Cash on hand minus the minimum reserve, floored at zero. Every later stage is bounded by this number by construction — the pipeline never produces a plan that spends more than it, so no post-hoc capping or scale-down step exists anywhere. (This is the design's central property; see the trap warning at the bottom.)
2 · Cashflow bucketing
The candidate durations (CD preset: 3/6/9/12/24/36/48/60 months; treasury: 3/6/12/24/36/60) define date boundaries from the reference date, and the boundaries define consecutive buckets. Every known cashflow — revenue, expense, or maturing holding — is summed into a bucket. Buckets are half-open: an expense lands in the bucket containing its start date.
3 · Buffering — conservatism where the uncertainty lives
Safety margin is applied by inflating expenses, not by shaving the investable amount: each expense bucket is multiplied by (1 + buffer), with a smaller buffer for short horizons where estimates are firmer (default 15% under 12 months) and a larger one for long horizons (30% at or beyond). Expenses are the uncertain quantity, so that is where the conservatism belongs; a flat haircut on investable cash would penalize every allocation equally regardless of when the risk occurs and leave the liquidity analysis itself unprotected.
4 · Liquidity needs — cumulative buffered shortfalls
Walk the boundaries in date order, accumulating buffered net cashflow (revenue plus maturities minus buffered expenses). Whenever the cumulative sum dips below zero, that deficit is a liquidity need: that many dollars must have matured back into cash by that horizon. Emit the pair (duration, amount) and reset the sum to zero. The result is a small list of duration-tagged amounts describing exactly when cash must be liquid.
5 · Capped greedy fill
Fill the liquidity needs in increasing duration order from the investable cash — each fill is min(need, remaining) — and send whatever remains after all needs are met to the longest duration, where yield is highest. Because remaining cash only decreases, the total allocated can never exceed investable cash.
6 · Geometric laddering
A single large allocation at one maturity concentrates reinvestment risk at one date and forfeits interim liquidity, so each raw allocation is redistributed down the ladder of eligible shorter durations with geometric weights: half stays at the original duration, a quarter at the next shorter, an eighth at the next, with the final weight repeated so the weights sum to exactly one. Laddered amounts still sum to the pre-ladder total, preserving the cap. Amounts are then rounded down to $1,000 purchase lots — never to nearest, which is what keeps the cap intact through rounding — with sub-lot residue staying in cash.
7 · Product matching
Each laddered duration amount is matched against the live product catalog: products of the chosen type maturing after the reference date and at or before the duration's target, ordered by yield descending. Treasuries take the single highest-yield match; CDs go through the FDIC split below. If nothing matches, the duration amount is still reported — unmatched — so the human sees the full intended portfolio shape.
FDIC rules (CDs only)
FDIC insurance covers up to $250,000 per depositor per institution. The allocator enforces:
- The cap applies only to CDs; treasuries are government-backed and exempt.
- The cap is tracked across all durations together: $200K to Bank A at 3 months leaves $50K of Bank A room at every other duration.
- Existing holdings count first: current per-institution CD balances are seeded into the running totals before any new allocation consumes room.
- Within a duration, candidates are consumed greedily, highest yield first: each institution receives
min(remaining amount, remaining room). - If every candidate's room is exhausted and an amount remains, the overflow lands on the highest-yield institution anyway with an advisory warning — the cap is guidance, and the human executing the purchases decides whether to accept uninsured exposure or split manually.
Worked example
$500,000 cash, $100,000 reserve → $400,000 investable. Durations 3, 12, 36 months.
- Liquidity needs: cumulative buffered net at 3 months is −$50,000 → emit a $50,000 need at 3 months, reset. At 12 months the sum is +$30,000 → no need.
- Fill: $50,000 to 3 months; the remaining $350,000 to 36 months.
- Ladder: the 3-month allocation has nothing beneath it and stays put. The 36-month allocation spreads over {36, 12, 3} with weights {½, ¼, ¼}: $175,000 / $87,500 / $87,500. Aggregated: 3 mo $137,500 · 12 mo $87,500 · 36 mo $175,000 — exactly $400,000. Lot rounding: $137,000 / $87,000 / $175,000, $1,000 residue stays in cash.
- FDIC: for the 3-month $137,000 in CD mode, if best-yield Bank A already holds $200,000 of the association's CDs, it has $50,000 of room and receives $50,000; the remaining $87,000 goes to next-best Bank B. Bank A is now at $250,000 and is skipped for every later duration.
Where the live code deviates from this description
A page written from the clean design alone would be wrong in two respects. Both are load-bearing to what the algorithm actually recommends.
- Expenses lag revenue and maturities by one bucket. The live liquidity walk nets, at boundary j:
revenues[j] + maturities[j] − (1 + buffer)·expenses[j−1]— expenses come from the prior bucket. Consequences: the first bucket's revenue/maturity is never counted, the last expense bucket is never counted, and the longest-duration boundary is never evaluated for a need (leftover cash lands there via the fill regardless). This reads as an intentional conservative-timing choice — require the cash liquid one bucket before the expense — but it has not been independently confirmed as intended. Treat it as behavior to preserve; do not "fix" it to same-bucket netting without a domain-owner ruling. - The buffer tier is chosen by the next bucket's span, not the expense's own horizon. The walk selects the long-term buffer when the following interval spans ≥ 12 months, and applies it to the lagged expense bucket — so under the default CD preset the 9→12-month expense bucket gets the 30% long buffer despite being nominally "under 12 months". This looks more like an artifact than a design choice; flagged for review rather than relied upon.
The accepted approximation, and what is deliberately unbuilt
- Greedy vs. the true optimum. The greedy fill hands the highest-yield candidates to whichever duration is processed first. The true optimum is a global LP/knapsack optimizer maximizing total yield across all durations subject to the liquidity needs and the FDIC cap. Greedy is the deliberately accepted approximation — simple, auditable, and its yield loss is small at HOA portfolio sizes; the LP is the known upgrade path when sizes justify it.
- Cash-management mode is unimplemented — it short-circuits with a warning and an empty allocation list.
- The reported "available to invest" is the allocated sum, not cash minus reserve: lot rounding leaves sub-$1,000 residue in cash, so the figure can sit slightly below the theoretical investable amount. Frontends must not recompute it.
- Non-goals: hard-blocking on the FDIC cap, trading/rebalancing/tax optimization, and forecasting cashflows — the allocator consumes the schedule it is given and buffers it, nothing more.
Trap warning. An earlier allocator sized each duration independently from projected cash, shaved investable cash by (1 − buffer), and proportionally scaled everything down when the sum exceeded available cash. It lost on structural grounds: the flat haircut puts conservatism in the wrong place, and post-hoc rescaling transiently recommends more than exists, distorts inter-duration ratios, and caused a real capping bug. Do not resurrect the proportional scale-down when "fixing" allocation totals — it is the known bug pattern, not a fallback. The current design also loads all inputs server-side from the property's rows, so the recommendation cannot disagree with the database.
Where it runs: a vendored, dependency-free backend package behind POST /api/investments/allocations, also consumed by the board packet, the management dashboard, and platform-admin tooling. Constants: $100K reserve, 0.15/0.30 buffers, $250K FDIC limit.