Initial PuGa toolkit: data layer, econ, depth-aware scan, state sync, plan push
- puga/: cached FIO + PRUNplanner clients, market view with order-book walk, econ formulas ported from PRUNplanner (tested against its suite and live FIO), saturation model v1 (reviewed by Opus) - tools/: scan (depth-aware), price, book, chain, state sync, plan_push (dry run default, [PuGa]-prefixed plans only), legacy prun_scan/prun_cxarb - docs/: mechanics (PRUNplanner is source of truth), roadmap, decisions, saturation design, archived handoff - secrets stay in .env (gitignored); ref/ holds PRUNplanner source (ignored) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
"""Saturation model v1 (docs/saturation-design.md, after Opus review 2026-09-18). Pure functions.
|
||||
Idea: N buildings is limited by (a) share of the market's steady traded flow, (b) the queue of existing
|
||||
sell orders (supply days) and (c) whether buy-side stock covers it. Price is a haircut model, not a book walk."""
|
||||
import math
|
||||
|
||||
S_SHARE = 0.25 # max share of traded flow one producer takes
|
||||
T_Q = 7 # days of flow the standing sell queue is compared against
|
||||
T_D = 7 # days of flow standing demand should cover
|
||||
T_W = 3 # patient-selling window (days)
|
||||
|
||||
|
||||
def tref(traded7: float, traded30: float) -> float:
|
||||
"""Conservative daily flow: min(7d, 30d); with a Poisson lower bound when 30d volume is small."""
|
||||
t = min(traded7, traded30)
|
||||
if 0 < traded30 < 20:
|
||||
t = min(t, max(0.0, traded30 * (1 - 1.96 / math.sqrt(30 * traded30))))
|
||||
return max(0.0, t)
|
||||
|
||||
|
||||
def is_thin(tr: float, demand: float, q_out: float) -> bool:
|
||||
return tr < 3 * q_out or demand < 7 * q_out
|
||||
|
||||
|
||||
def n_out(tr: float, supply: float, demand: float, q_out: float) -> float:
|
||||
"""Buildings the output market absorbs: flow share x queue penalty x demand coverage. 0 if no flow."""
|
||||
if tr <= 0 or q_out <= 0:
|
||||
return 0.0
|
||||
base = S_SHARE * tr / q_out
|
||||
queue = min(1.0, T_Q * tr / supply) if supply > 0 else 1.0
|
||||
cover = min(1.0, demand / (T_D * tr))
|
||||
return base * queue * cover
|
||||
|
||||
|
||||
def p_patient(asks: list[tuple[float, float]], tr: float, produced_per_day: float, bid: float, vwap7: float | None,
|
||||
vwap30: float | None, ask: float | None, wide_high: float | None = None) -> float:
|
||||
"""Price we can hold asks at. asks = [(price, units)] ascending. Find the highest ask level whose units-ahead
|
||||
(levels strictly below) <= what buyers will absorb of the queue over T_W days once our output is counted:
|
||||
target = T_W * (tr - produced). target <= 0 => sell at the bid. Clamped to [bid, min(vwap7, vwap30, ask, wide_high)]."""
|
||||
hi = min(x for x in (vwap7, vwap30, ask, wide_high) if x)
|
||||
target = T_W * (tr - produced_per_day)
|
||||
if target <= 0:
|
||||
return bid
|
||||
p, ahead = hi, 0.0
|
||||
for price, units in asks:
|
||||
if ahead <= target:
|
||||
p = price
|
||||
ahead += units
|
||||
return max(bid, min(p, hi))
|
||||
|
||||
|
||||
def effective_supply(asks: list[tuple[float, float]], ref_price: float | None, band: float = 1.25) -> float:
|
||||
"""Units of standing sell orders that actually compete: price <= band x reference (vwap7 or ask).
|
||||
Stale asks far above market (e.g. 1500 units at 1.5x vwap) are not competition. asks = [(price, units)]."""
|
||||
if not ref_price:
|
||||
return sum(u for _, u in asks)
|
||||
return sum(u for p, u in asks if p <= band * ref_price)
|
||||
Reference in New Issue
Block a user