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,53 @@
|
||||
"""Unified market view: FIO book totals merged with PRUNplanner VWAP/volume, plus order-book walks."""
|
||||
from dataclasses import dataclass
|
||||
from . import fio, prunplanner as pp
|
||||
|
||||
CXS = ["AI1", "NC1", "CI1", "IC1", "NC2", "CI2"]
|
||||
|
||||
|
||||
@dataclass
|
||||
class Quote:
|
||||
tk: str
|
||||
cx: str
|
||||
ask: float | None = None
|
||||
bid: float | None = None
|
||||
supply: float = 0 # units on sell side (order-book total)
|
||||
demand: float = 0 # units on buy side
|
||||
vwap7: float | None = None
|
||||
vwap30: float | None = None
|
||||
traded7: float = 0 # avg units/day over 7d
|
||||
traded30: float = 0 # avg units/day over 30d
|
||||
mm_buy: float | None = None
|
||||
mm_sell: float | None = None
|
||||
|
||||
|
||||
def snapshot(refresh=False) -> dict[tuple[str, str], Quote]:
|
||||
q: dict[tuple[str, str], Quote] = {}
|
||||
for e in fio.exchange_all(refresh):
|
||||
k = (e["MaterialTicker"], e["ExchangeCode"])
|
||||
q[k] = Quote(k[0], k[1], e.get("Ask"), e.get("Bid"), e.get("Supply") or 0, e.get("Demand") or 0,
|
||||
mm_buy=e.get("MMBuy"), mm_sell=e.get("MMSell"))
|
||||
for e in pp.exchanges(refresh):
|
||||
k = (e["ticker"], e["exchange_code"])
|
||||
x = q.setdefault(k, Quote(k[0], k[1], e.get("ask"), e.get("bid"), e.get("supply") or 0, e.get("demand") or 0))
|
||||
x.vwap7, x.vwap30 = e.get("vwap_7d"), e.get("vwap_30d")
|
||||
x.traded7, x.traded30 = e.get("avg_traded_7d") or 0, e.get("avg_traded_30d") or 0
|
||||
return q
|
||||
|
||||
|
||||
def walk(mat: str, cx: str, qty: float, side: str, refresh=False) -> dict:
|
||||
"""Walk the live order book. side='buy' takes sell orders (you pay asks); side='sell' hits buy orders (you get bids).
|
||||
Returns avg price, units filled, worst price reached, total cost/proceeds. Shallow books give filled < qty."""
|
||||
ob = fio.order_book(mat, cx, refresh)
|
||||
orders = ob["SellingOrders"] if side == "buy" else ob["BuyingOrders"]
|
||||
# MM orders come with ItemCount None = unlimited depth; treat as infinite
|
||||
orders = sorted((o for o in orders if o.get("ItemCount") != 0), key=lambda o: o["ItemCost"], reverse=(side == "sell"))
|
||||
left, total, worst = qty, 0.0, None
|
||||
for o in orders:
|
||||
take = min(left, float("inf") if o.get("ItemCount") is None else o["ItemCount"])
|
||||
total += take * o["ItemCost"]
|
||||
worst, left = o["ItemCost"], left - take
|
||||
if left <= 0:
|
||||
break
|
||||
filled = qty - left
|
||||
return dict(avg=total / filled if filled else None, filled=filled, worst=worst, total=total, short=left > 0)
|
||||
Reference in New Issue
Block a user