SPB Git

spb/wp25_uqo

Public
8.1 KB · 178 lines python
1"""Deterministic ledger for Model C: investors, one bank, one risky asset in fixed supply, a safe deposit/loan rate.23Balance sheets4--------------5Investor i : assets  = deposits c_i + p h_i ;  liabilities = loan_i ;  equity_i = c_i + p h_i - loan_i.6Bank       : assets  = sum loan_i + reserves ;  liabilities = sum c_i + exchange account F + bank equity E_B.7Reserves are the bank's claim on the exogenous safe technology; they change only through *injections* (dividends paid8by the asset issuer into investors' deposits, interest earned on reserves).  Every other operation is a transfer.910Invariants (asserted after every operation):11  (I1) sum h_i == S                                   (no asset creation; no short sales in this version)12  (I2) sum c_i + F + E_B == sum loan_i + reserves     (bank balance sheet)13  (I3) reserves == reserves_0 + cumulative injections (cash is only created by the outside sources)14Margin rule: loan_i <= (1 - m) p h_i.  A breach triggers a forced sale at the current price (with optional price15impact); if the loan cannot be repaid the residual is written off against bank equity (default); if bank equity turns16negative, depositors are bailed in pro rata.  Bank lending capacity: E_B >= k_B * (loans + reserves) (leverage ratio).17"""18from __future__ import annotations1920import numpy as np212223class Ledger:24    def __init__(self, n: int, cash0, holdings0, bank_equity0: float, margin: float, fee_rate: float = 0.0,25                 price0: float = 1.0, rf: float = 0.0, bank_capital_ratio: float = 0.0):26        self.n = n27        self.cash = np.array(cash0, dtype=float)28        self.holdings = np.array(holdings0, dtype=float)29        self.loan = np.zeros(n)30        self.S = float(self.holdings.sum())31        self.margin = margin32        self.fee_rate = fee_rate33        self.rf = rf34        self.price = float(price0)35        self.bank_equity = float(bank_equity0)36        self.F = 0.0                                        # exchange account (fees)37        self.reserves = float(self.cash.sum() + bank_equity0)38        self._reserves0 = self.reserves39        self.injections = 0.040        self.k_B = bank_capital_ratio41        self.loan_losses = 0.042        self.deposit_haircuts = 0.043        self.defaults = 044        self.forced_sales = 0.045        self.alive = np.ones(n, dtype=bool)46        self.check()4748    # ---------------------------------------------------------------- identities49    def equity(self):50        return self.cash + self.price * self.holdings - self.loan5152    def check(self):53        tol = 1e-7 * max(1.0, abs(self.reserves))54        assert abs(self.holdings.sum() - self.S) < 1e-7 * max(1.0, self.S), 'I1 holdings'55        assert np.all(self.holdings >= -1e-9), 'I1 short'56        assert np.all(self.loan >= -1e-9), 'negative loan'57        lhs = self.cash.sum() + self.F + self.bank_equity58        rhs = self.loan.sum() + self.reserves59        assert abs(lhs - rhs) < tol, f'I2 bank balance sheet {lhs} vs {rhs}'60        assert abs(self.reserves - (self._reserves0 + self.injections)) < tol, 'I3 reserves'6162    # ---------------------------------------------------------------- outside flows (injections)63    def pay_dividend(self, d: float):64        amt = d * self.holdings65        self.cash += amt66        self.reserves += amt.sum()67        self.injections += amt.sum()68        self.check()6970    def accrue_interest(self):71        """Deposits earn rf, loans cost rf; reserves earn rf from the safe technology (injection)."""72        dep_int = self.rf * self.cash73        loan_int = self.rf * self.loan74        res_int = self.rf * self.reserves75        self.cash += dep_int - loan_int76        self.reserves += res_int77        self.injections += res_int78        self.bank_equity += res_int + loan_int.sum() - dep_int.sum()79        self.check()8081    # ---------------------------------------------------------------- credit82    def loan_capacity(self, i: int) -> float:83        cap_margin = max((1 - self.margin) * self.price * self.holdings[i] - self.loan[i], 0.0)84        if self.k_B > 0:85            # E_B >= k_B (loans + x + reserves)  ->  x <= E_B/k_B - loans - reserves86            cap_bank = max(self.bank_equity / self.k_B - self.loan.sum() - self.reserves, 0.0)87            cap_margin = min(cap_margin, cap_bank)88        return cap_margin8990    def borrow(self, i: int, amount: float) -> float:91        amt = float(min(max(amount, 0.0), self.loan_capacity(i)))92        self.loan[i] += amt93        self.cash[i] += amt94        self.check()95        return amt9697    def repay(self, i: int, amount: float) -> float:98        amt = float(min(max(amount, 0.0), self.loan[i], max(self.cash[i], 0.0)))99        self.loan[i] -= amt100        self.cash[i] -= amt101        self.check()102        return amt103104    # ---------------------------------------------------------------- trading105    def settle_trade(self, buyer: int, seller: int, qty: float, price: float) -> bool:106        if qty <= 0 or buyer == seller or price <= 0:107            return False108        cost = qty * price109        fee = self.fee_rate * cost110        if self.cash[buyer] + 1e-12 < cost + fee or self.holdings[seller] + 1e-12 < qty:111            return False112        self.cash[buyer] -= cost + fee113        self.cash[seller] += cost - fee114        self.F += 2 * fee115        self.holdings[buyer] += qty116        self.holdings[seller] -= qty117        self.holdings[seller] = max(self.holdings[seller], 0.0)118        self.price = price119        self.check()120        return True121122    def max_affordable(self, i: int, price: float) -> float:123        return max(self.cash[i], 0.0) / (price * (1 + self.fee_rate))124125    # ---------------------------------------------------------------- margin, default, bail-in126    def mark_to_market(self, price: float, liquidation_impact: float = 0.0) -> dict:127        self.price = float(price)128        forced = losses = 0.0129        nd = 0130        for i in range(self.n):131            breach = self.loan[i] - (1 - self.margin) * self.price * self.holdings[i]132            if breach <= 1e-10:133                continue134            x = min(self.holdings[i], breach / max(self.margin * self.price, 1e-12))135            exec_price = self.price * max(1 - liquidation_impact * x / max(self.S, 1e-12), 0.05)136            others = [j for j in range(self.n) if j != i and self.alive[j] and self.cash[j] > 0]137            if x > 0 and others:138                w = np.array([self.cash[j] for j in others])139                proceeds = x * exec_price140                if w.sum() < proceeds:            # buyers' cash limits absorption141                    x = w.sum() / exec_price142                    proceeds = x * exec_price143                share = w / w.sum()144                self.holdings[i] -= x145                self.cash[i] += proceeds146                for j, s in zip(others, share):147                    self.holdings[j] += x * s148                    self.cash[j] -= proceeds * s149                self.price = exec_price150                forced += x151                self.forced_sales += x152            self.repay(i, self.loan[i])153            if self.loan[i] > 1e-10 and (self.cash[i] <= 1e-10 or self.equity()[i] < 0):154                loss = self.loan[i]155                self.loan[i] = 0.0156                self.bank_equity -= loss157                self.loan_losses += loss158                losses += loss159                self.defaults += 1160                nd += 1161                self.alive[i] = False162        if self.bank_equity < -1e-10:163            short = -self.bank_equity164            dep = np.maximum(self.cash, 0.0)165            if dep.sum() > 0:166                cut = min(short, dep.sum())167                self.cash -= cut * dep / dep.sum()168                self.deposit_haircuts += cut169                self.bank_equity += cut170        self.check()171        return dict(forced=forced, losses=losses, defaults=nd)172173    def snapshot(self):174        return dict(cash=self.cash.copy(), holdings=self.holdings.copy(), loan=self.loan.copy(), price=self.price,175                    bank_equity=self.bank_equity, reserves=self.reserves, defaults=self.defaults,176                    forced_sales=self.forced_sales, loan_losses=self.loan_losses, fees=self.F,177                    deposit_haircuts=self.deposit_haircuts, leverage=float(self.loan.sum() / max(self.price * self.S, 1e-12)))178
Files · main
Menu

Theme