- puga/contd.py, tools/contd.py: price open CONTD contract offers against real buy-fresh cost, flagging conditions that can't be priced from the API alone. - tools/buyat.py: cheapest bid price likely to fill by a hard deadline (stockout risk), using the empirical first-passage model from history.py. - puga/actpkg.py, tools/actpkg.py: Refined PrUn XIT ACT buy-list JSON packages. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
84 lines
4.6 KiB
Python
84 lines
4.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Where to post a BUY order (a resting bid) when there's a hard DEADLINE - not "is patience
|
|
worth it forever" (that's tools/sell.py's problem, where foregone margin is the only cost of
|
|
waiting). Here, a bid that never fills means a production-line STOCKOUT (check tools/runway.py
|
|
for your real deadline), so the right question is: what's the cheapest price that's still likely
|
|
to fill BY that deadline, with paying the ask as the fallback if it doesn't?
|
|
|
|
puga buyat PG 150 --deadline 3 # 3 days of runway left on PG
|
|
|
|
For each candidate price between the current ask and best bid, uses puga.history.empirical_touch
|
|
with a fixed `horizon` (the deadline) to get the REAL empirical P(price actually falls to this
|
|
level within that many days) - not "ever", which is the wrong question under a deadline. Expected
|
|
cost = price * P(fill by deadline) + current_ask * (1 - P(fill by deadline)) - the fallback term
|
|
assumes paying the ask at the deadline if the bid never filled; the ask itself could move by then,
|
|
so this is an approximation, not a guarantee. Recommends the price minimizing expected cost, but
|
|
shows the whole curve - pick a more conservative (higher-P) price than the cost-minimizer if a
|
|
stockout would be expensive in ways this doesn't price in (e.g. the whole base's workforce idling).
|
|
"""
|
|
import argparse, sys
|
|
from pathlib import Path
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
from puga import config, fio, market
|
|
from puga.history import daily_prices, full_series, empirical_touch
|
|
import math
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument("ticker")
|
|
ap.add_argument("qty", type=float)
|
|
ap.add_argument("--deadline", type=float, required=True, help="days until this input runs out (tools/runway.py) - the hard constraint, not a soft discount rate")
|
|
ap.add_argument("--cx", default=config.DEFAULT_CX)
|
|
a = ap.parse_args()
|
|
t = a.ticker.upper()
|
|
ob = fio.order_book(t, a.cx)
|
|
bids = sorted(((o["ItemCost"], o["ItemCount"] or 0) for o in ob["BuyingOrders"] if o.get("ItemCost")), reverse=True)
|
|
best_bid = bids[0][0] if bids else None
|
|
best_ask = ob.get("Ask")
|
|
|
|
hit = market.walk(t, a.cx, a.qty, "buy")
|
|
ask_cost = hit["total"] if not hit["short"] else None
|
|
print(f"{t}.{a.cx} best bid {best_bid} ask {best_ask} vwap7 {ob.get('PriceAverage')} deadline {a.deadline:g}d")
|
|
if ask_cost is not None:
|
|
print(f"instant (pay the asks now): {ask_cost:,.0f} (avg {hit['avg']:.0f}) - the fallback if nothing fills in time")
|
|
else:
|
|
print("WARNING: book too thin to fill this quantity at any price right now - the 'pay the ask' fallback isn't actually available at this size")
|
|
|
|
dp = daily_prices(t, a.cx)
|
|
s0 = sorted(dp.items())[-1][1][0] if dp else (best_bid or ob.get("Bid") or 0)
|
|
series = full_series(t, a.cx)
|
|
if len(series) < 10:
|
|
print("\nnot enough price history to estimate fill probability - fall back to paying the ask")
|
|
return
|
|
|
|
candidates = sorted({p for p, _ in bids if p <= s0} | {round(s0 * m, 0) for m in (0.99, 0.97, 0.95, 0.9, 0.85, 0.8, 0.7, 0.6)})
|
|
candidates = [p for p in candidates if p > 0 and (best_bid is None or p >= best_bid * 0.5)]
|
|
|
|
print(f"\nlast traded {s0:.0f}, {len(series)}d history - P(fill) is specifically BY the {a.deadline:g}d deadline, not ever:")
|
|
print(f" {'price':>10} {'P(fill by deadline)':>20} {'cost if filled':>15} {'expected cost':>14}")
|
|
rows = []
|
|
for p in candidates:
|
|
y = math.log(p / s0) if s0 else 0.0
|
|
if y >= 0:
|
|
p_fill = 1.0
|
|
else:
|
|
p_fill = empirical_touch(series, y, 0.0, horizon=a.deadline)["touched_frac"]
|
|
cost_if_filled = p * a.qty
|
|
fallback = ask_cost if ask_cost is not None else p * a.qty * 1.5 # crude: no real fallback price known
|
|
expected = p_fill * cost_if_filled + (1 - p_fill) * fallback
|
|
rows.append((p, p_fill, cost_if_filled, expected))
|
|
|
|
best = min(rows, key=lambda r: r[3]) if rows else None
|
|
for p, p_fill, cost_if_filled, expected in rows:
|
|
flag = " <- min expected cost" if (p, p_fill, cost_if_filled, expected) == best else ""
|
|
print(f" {p:>10.0f} {100*p_fill:>19.0f}% {cost_if_filled:>15,.0f} {expected:>14,.0f}{flag}")
|
|
|
|
print("\nNOTE: 'expected cost' assumes paying today's ask as the fallback if the bid never fills by the "
|
|
"deadline - the real ask could move by then, this is an estimate. Pick a price with HIGHER P(fill) "
|
|
"than the cost-minimizer if a stockout costs more than this model sees (idle workforce, missed orders).")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|