#!/usr/bin/env python3 """ Interactive Financial Stress-Testing Dashboard Single-file web app that models: - Net worth projections with BTC scenarios + h-clock cycle trade - Cashflow and break-even analysis - Stress tests + Monte Carlo BTC outcomes - Cycle trade simulator - BTC-denominated scoreboard Run: python /home/ben/ava/fund-model/app.py CLI: --host 0.0.0.0 --port 8235 --log-level info """ from __future__ import annotations import argparse import json import logging from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer from urllib.parse import urlparse LOGGER = logging.getLogger("fund-model") DEFAULTS = { # Core portfolio defaults from current plan "btcCagr": 25, "btcStack": 22.19, "btcPrice": 97_000, "strdPosition": 500_000, "strdYield": 14.66, "cash": 100_000, # Income / expenses "salaryGross": 240_000, "salaryNet": 170_000, "expensesNeeds": 75_000, "expensesWants": 58_000, "monthlyDca": 500, # Projection controls "horizonYears": 7, "quitJob": False, # h-clock cycle trade assumptions "cycleBottomMin": 25_000, "cycleBottomMax": 60_000, "cycleTopMin": 150_000, "cycleTopMax": 400_000, "cycleCapital": 500_000, "tradeDurationMonths": 18, "taxRate": 33, # Monte Carlo controls "mcVolatility": 65, "mcPaths": 1000, } HTML_TEMPLATE = r""" Fund Model — Stress Dashboard
Base NW (horizon)
Pessimistic NW
Optimistic NW
Base BTC Stack (horizon)

Net Worth Path with BTC Price Scenarios + h-clock Trade

Band = pessimistic to optimistic. Base uses your slider CAGR. Model applies STRD → IBIT cycle trade (sell Aug 2026, buy bottom window, sell late 2027 with LTCG tax).
Monthly Surplus
Annual Surplus
BTC Buy Capacity / Year
Passive Coverage (expenses)

Cashflow Breakdown

NW if BTC -50%
NW if BTC -70%
NW if BTC -80%
Runway if job loss
Cashflow if STRD = 0
Worst case (all 3)

Monte Carlo (N paths, 5 years)

Geometric Brownian Motion with drift from BTC CAGR and volatility input. Shows distribution of year-5 net worth.
Base BTC Bought
Base Net Profit (after tax)
STRD Hold Yield (same period)
Cycle Advantage vs STRD

Cycle Trade Scenarios

Scenario Bottom Top BTC Bought Gross Profit Tax Net Profit

Cycle Net Profit vs STRD Counterfactual

Real BTC (on chain / custody)
NW in BTC terms
Real / NW %
Sat accrual (salary, monthly)
Sat accrual (net, monthly)
BTC yield from cycle trade (base)

Real BTC vs Fiat-Equivalent BTC

""" def build_html() -> bytes: html = HTML_TEMPLATE.replace("__DEFAULTS__", json.dumps(DEFAULTS)) return html.encode("utf-8") class AppHandler(BaseHTTPRequestHandler): def _send(self, status: int, content_type: str, body: bytes = b"", head_only: bool = False) -> None: self.send_response(status) self.send_header("Content-Type", content_type) self.send_header("Content-Length", str(len(body))) self.end_headers() if not head_only and body: self.wfile.write(body) def do_GET(self) -> None: parsed = urlparse(self.path) if parsed.path in {"/", "/index.html"}: self._send(200, "text/html; charset=utf-8", build_html()) return if parsed.path == "/healthz": payload = json.dumps({"ok": True}).encode("utf-8") self._send(200, "application/json", payload) return self._send(404, "text/plain; charset=utf-8", b"Not Found") def do_HEAD(self) -> None: parsed = urlparse(self.path) if parsed.path in {"/", "/index.html"}: self._send(200, "text/html; charset=utf-8", build_html(), head_only=True) return if parsed.path == "/healthz": payload = json.dumps({"ok": True}).encode("utf-8") self._send(200, "application/json", payload, head_only=True) return self._send(404, "text/plain; charset=utf-8", b"Not Found", head_only=True) def log_message(self, fmt: str, *args) -> None: LOGGER.info("%s - %s", self.address_string(), fmt % args) def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser(description="Interactive fund model dashboard") parser.add_argument("--host", default="0.0.0.0", help="Host to bind (default: 0.0.0.0)") parser.add_argument("--port", default=8235, type=int, help="Port to bind (default: 8235)") parser.add_argument( "--log-level", default="info", choices=["debug", "info", "warning", "error", "critical"], help="Logging verbosity", ) return parser.parse_args() def main() -> None: args = parse_args() logging.basicConfig( level=getattr(logging, args.log_level.upper(), logging.INFO), format="%(asctime)s %(levelname)s %(name)s: %(message)s", ) server = ThreadingHTTPServer((args.host, args.port), AppHandler) LOGGER.info("Serving dashboard on http://%s:%s", args.host, args.port) try: server.serve_forever() except KeyboardInterrupt: LOGGER.info("Shutting down...") finally: server.server_close() if __name__ == "__main__": main()