""" Lottery Investment Calculator — pure calculation logic. All functions are deterministic and side-effect free. Tax rates, exchange rates, and investment defaults are passed as explicit parameters (with sensible defaults) so that callers can override via config. """ from __future__ import annotations # --------------------------------------------------------------------------- # Core calculators # --------------------------------------------------------------------------- def _run_investment_cycles( principal: float, annual_return: float, cycles: int, investment_income_tax_rate: float, personal_withdrawal_pct: float, ) -> tuple[list[dict], float, float]: """Simulate 90-day reinvestment cycles. Returns: (cycle_results, total_personal_withdrawals, final_principal) """ cycle_results: list[dict] = [] total_personal_withdrawals = 0.0 for cycle in range(1, cycles + 1): interest_earned = principal * annual_return * (90 / 365) taxes_owed = interest_earned * investment_income_tax_rate personal_withdrawal = interest_earned * personal_withdrawal_pct total_withdrawal = taxes_owed + personal_withdrawal reinvestment = interest_earned - total_withdrawal new_principal = principal + reinvestment total_personal_withdrawals += personal_withdrawal cycle_results.append( { "cycle": cycle, "principalStart": principal, "interestEarned": interest_earned, "taxesOwed": taxes_owed, "personalWithdrawal": personal_withdrawal, "totalWithdrawal": total_withdrawal, "reinvestment": reinvestment, "principalEnd": new_principal, } ) principal = new_principal return cycle_results, total_personal_withdrawals, principal def calculate_us_lottery( jackpot: float, invest_percentage: float = 0.90, annual_return: float = 0.045, cycles: int = 8, *, state_tax_rate: float = 0.055, lump_sum_rate: float = 0.52, federal_tax_rate: float = 0.37, usd_cad_rate: float = 1.35, investment_income_tax_rate: float = 0.5353, personal_withdrawal_pct: float = 0.10, ) -> dict: """Calculate investment returns for US lottery winnings. Args: jackpot: Advertised jackpot amount (USD). invest_percentage: Fraction to invest (0-1). annual_return: Expected annual return rate. cycles: Number of 90-day reinvestment cycles. state_tax_rate: State income-tax rate on winnings. lump_sum_rate: Fraction of advertised jackpot available as lump sum. federal_tax_rate: Federal income-tax rate on winnings. usd_cad_rate: USD to CAD exchange rate. investment_income_tax_rate: Marginal tax on investment income. personal_withdrawal_pct: Fraction of interest withdrawn each cycle. """ cash_sum = jackpot * lump_sum_rate federal_tax = cash_sum * federal_tax_rate state_tax = cash_sum * state_tax_rate net_amount = cash_sum - federal_tax - state_tax canadian_amount = net_amount * usd_cad_rate investment_principal = canadian_amount * invest_percentage fun_money = canadian_amount * (1 - invest_percentage) cycle_results, total_withdrawals, final_principal = _run_investment_cycles( investment_principal, annual_return, cycles, investment_income_tax_rate, personal_withdrawal_pct, ) net_daily_income = (investment_principal * annual_return * (1 - investment_income_tax_rate)) / 365 return { "country": "US", "originalJackpot": jackpot, "cashSum": cash_sum, "federalTax": federal_tax, "stateTax": state_tax, "stateTaxRate": state_tax_rate, "netAmountUsd": net_amount, "netAmountCad": canadian_amount, "investmentPrincipal": investment_principal, "funMoney": fun_money, "netDailyIncome": net_daily_income, "annualIncome": net_daily_income * 365, "totalPersonalWithdrawals": total_withdrawals, "finalPrincipal": final_principal, "cycles": cycle_results, } def calculate_canadian_lottery( jackpot: float, invest_percentage: float = 0.90, annual_return: float = 0.045, cycles: int = 8, *, investment_income_tax_rate: float = 0.5353, personal_withdrawal_pct: float = 0.10, ) -> dict: """Calculate investment returns for Canadian lottery winnings (tax-free). Args: jackpot: Jackpot amount (CAD) - no tax deducted on winnings. invest_percentage: Fraction to invest (0-1). annual_return: Expected annual return rate. cycles: Number of 90-day reinvestment cycles. investment_income_tax_rate: Marginal tax on investment income. personal_withdrawal_pct: Fraction of interest withdrawn each cycle. """ net_amount = jackpot # Tax-free! investment_principal = net_amount * invest_percentage fun_money = net_amount * (1 - invest_percentage) cycle_results, total_withdrawals, final_principal = _run_investment_cycles( investment_principal, annual_return, cycles, investment_income_tax_rate, personal_withdrawal_pct, ) net_daily_income = (investment_principal * annual_return * (1 - investment_income_tax_rate)) / 365 return { "country": "Canada", "originalJackpot": jackpot, "netAmountCad": net_amount, "investmentPrincipal": investment_principal, "funMoney": fun_money, "netDailyIncome": net_daily_income, "annualIncome": net_daily_income * 365, "totalPersonalWithdrawals": total_withdrawals, "finalPrincipal": final_principal, "cycles": cycle_results, } # --------------------------------------------------------------------------- # Break-even calculator # --------------------------------------------------------------------------- def calculate_break_even( odds: int, ticket_cost: float, country: str = "us", *, lump_sum_rate: float = 0.52, federal_tax_rate: float = 0.37, state_tax_rate: float = 0.055, ) -> dict: """Calculate the jackpot where expected value >= ticket cost. For US lotteries the take-home fraction is:: lump_sum_rate * (1 - federal_tax_rate - state_tax_rate) For Canadian lotteries the full jackpot is kept (tax-free). Returns a dict with the break-even jackpot and supporting details. """ if country == "us": take_home_fraction = lump_sum_rate * (1 - federal_tax_rate - state_tax_rate) else: take_home_fraction = 1.0 # EV = (jackpot * take_home_fraction) / odds >= ticket_cost # => jackpot >= ticket_cost * odds / take_home_fraction break_even_jackpot = (ticket_cost * odds) / take_home_fraction probability = 1 / odds return { "breakEvenJackpot": break_even_jackpot, "takeHomeFraction": take_home_fraction, "odds": odds, "probability": probability, "ticketCost": ticket_cost, "expectedValueAtBreakEven": probability * break_even_jackpot * take_home_fraction, } # --------------------------------------------------------------------------- # Annuity calculator # --------------------------------------------------------------------------- def calculate_annuity( jackpot: float, country: str = "us", years: int = 30, annual_increase: float = 0.05, *, federal_tax_rate: float = 0.37, state_tax_rate: float = 0.055, ) -> dict: """Calculate a multi-year annuity payout schedule. Powerball / Mega Millions annuities pay an initial amount then increase each year by *annual_increase* (typically 5%). Returns yearly pre-tax and after-tax amounts plus totals. """ # Calculate initial annual payment using geometric series: # jackpot = payment * sum((1 + r)^k) for k=0..years-1 # = payment * ((1+r)^years - 1) / r if annual_increase > 0: geo_sum = ((1 + annual_increase) ** years - 1) / annual_increase else: geo_sum = float(years) initial_payment = jackpot / geo_sum schedule: list[dict] = [] total_pre_tax = 0.0 total_after_tax = 0.0 for year in range(1, years + 1): pre_tax = initial_payment * (1 + annual_increase) ** (year - 1) tax = pre_tax * (federal_tax_rate + state_tax_rate) if country == "us" else 0.0 after_tax = pre_tax - tax total_pre_tax += pre_tax total_after_tax += after_tax schedule.append( { "year": year, "preTax": pre_tax, "tax": tax, "afterTax": after_tax, } ) return { "jackpot": jackpot, "country": country, "years": years, "annualIncrease": annual_increase, "initialPayment": initial_payment, "totalPreTax": total_pre_tax, "totalAfterTax": total_after_tax, "schedule": schedule, } # --------------------------------------------------------------------------- # Group play calculator # --------------------------------------------------------------------------- def calculate_group_split( jackpot: float, members: int = 2, shares: list[float] | None = None, country: str = "us", *, lump_sum_rate: float = 0.52, federal_tax_rate: float = 0.37, state_tax_rate: float = 0.055, usd_cad_rate: float = 1.35, investment_income_tax_rate: float = 0.5353, personal_withdrawal_pct: float = 0.10, ) -> dict: """Split lottery winnings among *members* with optional custom shares. If *shares* is None every member gets an equal share. Otherwise *shares* must be a list of floats summing to ~1.0 with length == *members*. """ # Normalise shares if shares is None: share_list = [1.0 / members] * members else: if len(shares) != members: share_list = [1.0 / members] * members else: total = sum(shares) share_list = [s / total for s in shares] if total > 0 else [1.0 / members] * members member_results: list[dict] = [] for i, share in enumerate(share_list): member_jackpot = jackpot * share if country == "us": calc = calculate_us_lottery( member_jackpot, lump_sum_rate=lump_sum_rate, federal_tax_rate=federal_tax_rate, state_tax_rate=state_tax_rate, usd_cad_rate=usd_cad_rate, investment_income_tax_rate=investment_income_tax_rate, personal_withdrawal_pct=personal_withdrawal_pct, ) else: calc = calculate_canadian_lottery( member_jackpot, investment_income_tax_rate=investment_income_tax_rate, personal_withdrawal_pct=personal_withdrawal_pct, ) member_results.append( { "member": i + 1, "share": share, "jackpotShare": member_jackpot, "calculation": calc, } ) return { "originalJackpot": jackpot, "members": members, "shares": share_list, "country": country, "memberResults": member_results, }