#!/usr/bin/env python3 """Where to park idle cash as a buy-low/sell-high position while ships are busy or cash has no better immediate use. Scans every ticker for a big spread AND real trading liquidity, scored by an EMPIRICAL round-trip estimate from the ticker's own price history (no random-walk assumption - see puga/parkcash.py's docstring for the method and open gaps). tools/parkcash.py # AI1, default filters, top 25 by pessimistic daily % tools/parkcash.py --min-trade-days 15 # require real liquidity (half the window trading) """ import argparse, sys from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) from puga import config, parkcash def main(): ap = argparse.ArgumentParser() ap.add_argument("--cx", default=config.DEFAULT_CX) ap.add_argument("--min-spread", type=float, default=20.0, help="minimum bid/ask spread %% to consider") ap.add_argument("--min-trade-days", type=int, default=10, help="minimum trading days out of the window - filters stale-quote traps") ap.add_argument("--days", type=int, default=30, help="calendar-day window for liquidity stats (the empirical round-trip model itself uses full history)") ap.add_argument("--rate", type=float, default=0.0, help="opportunity cost of waiting on this cash - default 0.0 matches this tool's premise (idle cash, no better use right now); pass your realized capital growth rate (e.g. 0.05, see tools/sell.py --rate) if this money IS earmarked for something and waiting has a real cost") ap.add_argument("--max-wait", type=float, help="drop anything with a combined round-trip median wait above this many days") ap.add_argument("--top", type=int, default=25) a = ap.parse_args() rows = parkcash.scan(a.cx, a.min_spread, a.days, a.rate) rows = [r for r in rows if r["trade_days"] >= a.min_trade_days] if a.max_wait is not None: rows = [r for r in rows if r["wait_days"] <= a.max_wait] rows.sort(key=lambda r: -r["daily_pct"]) print(f"# {a.cx} spread>={a.min_spread:.0f}% trade_days>={a.min_trade_days}/{a.days}d rate={100*a.rate:.1f}%/day " f"(round trip = empirical buy-dip + sell-recovery from real history, see puga/parkcash.py)") print(f"{'daily%':>7} {'disc%':>6} {'margin%':>8} {'spread%':>8} {'days':>5} {'buy%':>5} {'sell%':>5} {'wait(d)':>7} {'bid':>9} {'ask':>9} tk") for r in rows[:a.top]: print(f"{r['daily_pct']:7.1f} {r['discounted_pct']:6.1f} {r['margin_pct']:8.1f} {r['spread_pct']:8.0f} {r['trade_days']:5d} " f"{100*r['buy_touch']:5.0f} {100*r['sell_touch']:5.0f} {r['wait_days']:7.1f} {r['bid']:9.0f} {r['ask']:9.0f} {r['tk']}") print("\nNOTE: disc% = margin% x real odds both legs ever happen (at --rate; a big gap from daily% " "means the speed number is flattering a trade that may never clear). buy%/sell% are each leg's " "real historical frequency. Not a compoundable rate - see docs/decisions.md. See puga/parkcash.py's " "OPEN GAPS (no fee data; legs treated as independent).") if __name__ == "__main__": main()