Add multi-base chain modelling, fix price-band-aware demand/supply, tranche-split sell tool
- puga/network.py + tools/network.py: model a multi-base chain (e.g. mine LST at one base, ship it, consume it at another). Simulates each base independently, nets a transferred material's producer-surplus against consumer-need, charges real freight only on what's moved, cm_free for a founding covered by a Core Module Kit. Worked example in plans/chains/. - puga/saturation.py: real price-band filtering. FIO's order_book NarrowPriceBandLow/High and WidePriceBandLow/High match APEX's own displayed Price Band exactly (verified live) - an order outside it is a stale artifact, not just uncompetitive. Added in_band() and effective_demand(); effective_supply() gained the same hard band filter alongside its existing soft vwap-proximity filter (renamed that param mult to free up `band`). Wired into tools/scan.py stage 2 in place of the raw, unfiltered demand figure. Was flagged as an unimplemented refinement in saturation-design.md since the original design review. - tools/sell.py: self-contained tranche split (aggressive tranche capped at a volume quantile, median normally or 80th pct with --tight when a payment is imminent and stockout risk outweighs margin; patient tranche priced under the next competitor tier). Instant-bid comparison moved behind --show-bid (off by default). - Docs and roadmap updated accordingly. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+26
-8
@@ -33,6 +33,7 @@ def main():
|
||||
ap.add_argument("--no-hq", action="store_true", help="ignore HQ from state (new base without the HQ)")
|
||||
ap.add_argument("--permits-used", type=int, help="override permits used (affects faction bonus multiplier), e.g. 2 for a second base")
|
||||
ap.add_argument("--deprec", type=float, default=0, help="demolish-later mode: building value decays linearly to 0 over this many days (game: ~60); subtracts capex/deprec per day")
|
||||
ap.add_argument("--input", help="only recipes consuming this ticker as an input, e.g. --input AL for recipes built on top of your own AL chain")
|
||||
ap.add_argument("--planet", help="planet natural id: adds extraction (EXT/COL/RIG) from its resources, uses its fertility and active COGC; new base (not in state) => no HQ, permits+1")
|
||||
ap.add_argument("--own", type=int, default=1, help="how many buildings WE would run; ROI is measured at this size (default 1). --min-n is only a market-size filter")
|
||||
ap.add_argument("--skip", default="", help="tiers left unstaffed, e.g. technician (no housing/wages; efficiency = staffed headcount share)")
|
||||
@@ -115,6 +116,8 @@ def main():
|
||||
continue
|
||||
ins = {i["Ticker"]: i["Amount"] for i in rec["Inputs"]}
|
||||
outs = {o["Ticker"]: o["Amount"] for o in rec["Outputs"]}
|
||||
if a.input and a.input.upper() not in ins:
|
||||
continue
|
||||
if any(not Q(t) or not Q(t).ask for t in ins) or any(not Q(t) or not Q(t).bid for t in outs):
|
||||
continue
|
||||
capex0 = bcost(b["Ticker"])
|
||||
@@ -170,23 +173,38 @@ def main():
|
||||
|
||||
cands.sort(key=lambda c: c["rough_roi"], reverse=True)
|
||||
rows = []
|
||||
book = {}
|
||||
book, bandcache = {}, {}
|
||||
|
||||
def raw_book(t):
|
||||
if t not in book:
|
||||
book[t] = fio.order_book(t, a.cx)
|
||||
return book[t]
|
||||
|
||||
def asks_of(t):
|
||||
if t not in book:
|
||||
book[t] = sorted((o["ItemCost"], (o["ItemCount"] or 0)) for o in fio.order_book(t, a.cx)["SellingOrders"])
|
||||
return book[t]
|
||||
return sorted((o["ItemCost"], (o["ItemCount"] or 0)) for o in raw_book(t)["SellingOrders"])
|
||||
|
||||
def band_of(t):
|
||||
if t not in bandcache:
|
||||
ob = raw_book(t)
|
||||
lo = ob.get("WidePriceBandLow") or ob.get("NarrowPriceBandLow")
|
||||
hi = ob.get("WidePriceBandHigh") or ob.get("NarrowPriceBandHigh")
|
||||
bandcache[t] = (lo, hi)
|
||||
return bandcache[t]
|
||||
|
||||
def demand_of(t):
|
||||
bids = ((o["ItemCost"], o["ItemCount"] or 0) for o in raw_book(t)["BuyingOrders"])
|
||||
return sat.effective_demand(bids, band_of(t))
|
||||
|
||||
for c in cands[:a.k]:
|
||||
io, mm = c["io"], c["mm"]
|
||||
# stage 2: queue penalty from competing asks near market price only
|
||||
# stage 2: queue penalty from competing asks near market price only, and demand within the tradeable band
|
||||
n2 = float("inf")
|
||||
for t, qo in io["out"].items():
|
||||
if t in mm:
|
||||
continue
|
||||
x = Q(t)
|
||||
se = sat.effective_supply(asks_of(t), x.vwap7 or x.ask)
|
||||
n2 = min(n2, sat.n_out(sat.tref(x.traded7, x.traded30), se, x.demand, qo))
|
||||
se = sat.effective_supply(asks_of(t), x.vwap7 or x.ask, band=band_of(t))
|
||||
n2 = min(n2, sat.n_out(sat.tref(x.traded7, x.traded30), se, demand_of(t), qo))
|
||||
c["n_lim"] = float(a.max_n) if n2 == float("inf") else n2
|
||||
if c["n_lim"] < a.min_n and not a.show_thin:
|
||||
continue
|
||||
@@ -198,7 +216,7 @@ def main():
|
||||
if t in mm:
|
||||
p = mm[t]
|
||||
else:
|
||||
p = sat.p_patient(asks_of(t), sat.tref(x.traded7, x.traded30), N * qo, x.bid, x.vwap7, x.vwap30, x.ask)
|
||||
p = sat.p_patient(asks_of(t), sat.tref(x.traded7, x.traded30), N * qo, x.bid, x.vwap7, x.vwap30, x.ask, band_of(t)[1])
|
||||
rev += N * qo * p
|
||||
cost = 0.0
|
||||
for t, qi in io["in"].items():
|
||||
|
||||
Reference in New Issue
Block a user