| |
| """ |
| scripts/verify_route.py — turn a survey hit into a decision (2026-07-29). |
| |
| venv/bin/python scripts/verify_route.py ETH |
| venv/bin/python scripts/verify_route.py 7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs |
| venv/bin/python scripts/verify_route.py ETH --rounds 5 --base USDC |
| |
| ──────────────────────────────────────────────────────────────────────────── |
| WHY THIS EXISTS |
| ──────────────────────────────────────────────────────────────────────────── |
| scripts/survey_pairs.py answered the breadth question — of 99 pairs, four |
| quoted positive: |
| |
| ETH +0.92 bps mSOL +0.92 bps PUMP +0.28 bps USDT +0.00 bps |
| |
| That is the first evidence this deployment has ever produced of a positive |
| round trip, and it is nowhere near enough to trade on. Three things stand |
| between that line and a route in the scan set, and this script checks all |
| three. |
| |
| 1. DECIMALS, FROM AN AUTHORITY |
| Every amount sent to Jupiter is `human_units * 10**decimals`. A value |
| wrong by one is a trade wrong by 10x, by two is 100x — in the direction |
| of borrowing far more than intended. A wrong MINT merely fails; a wrong |
| DECIMALS can succeed catastrophically. So it is read out of Jupiter's |
| own token list, never typed from memory. |
| |
| 2. DOES IT PERSIST? |
| One observation of +0.92 bps is one sample of a noisy process. Quote |
| noise between two calls seconds apart is routinely larger than the edge |
| being measured. Several rounds, spaced out, and the MEDIAN is what |
| counts — if the median is negative, the survey caught noise. |
| |
| 3. DOES IT SURVIVE SIZE? |
| This is the one that decides whether the edge is tradeable at all. The |
| survey quoted $1,000. The bot's SOLANA_REAL_LOAN_UNITS is 50,000. Price |
| impact grows superlinearly with size, so an edge measured small can be |
| gone — or inverted — at the size that would make it worth having. |
| Quoting the ladder is the only way to know where it breaks. |
| |
| The output is either an exact pair of env lines to paste, or a clear |
| refusal. Read-only: quotes only, this cannot sign, send or spend anything |
| beyond Jupiter API calls. |
| """ |
|
|
| from __future__ import annotations |
|
|
| import argparse |
| import asyncio |
| import os |
| import statistics |
| import sys |
| import time |
| from pathlib import Path |
| from typing import Any, Optional |
|
|
| sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) |
|
|
| try: |
| import httpx |
| except ImportError: |
| print("httpx not installed — run from the venv: venv/bin/python scripts/verify_route.py") |
| raise SystemExit(1) |
|
|
| from constants import JUPITER_HEADERS, JUPITER_QUOTE_API, SOLANA_TOKENS |
| from modules.env_file import effective_haircut_bps, load_env_file |
|
|
| |
| |
| load_env_file() |
|
|
| _TOKEN_LOOKUP_URLS = [ |
| "https://lite-api.jup.ag/tokens/v2/search?query={q}", |
| "https://tokens.jup.ag/token/{q}", |
| ] |
| |
| |
| _DEFAULT_SIZES = [1_000, 5_000, 10_000, 25_000, 50_000] |
|
|
|
|
| def _pace_secs() -> float: |
| try: |
| raw = float(os.getenv("SOLANA_JUPITER_MIN_INTERVAL_SECS", "") or 2.1) |
| except ValueError: |
| raw = 2.1 |
| fast = os.getenv("SOLANA_JUPITER_ALLOW_FAST_PACING", "").strip().lower() in ( |
| "1", "true", "yes", "on", |
| ) |
| return raw if fast else max(2.0, raw) |
|
|
|
|
| async def resolve_token(client: httpx.AsyncClient, query: str) -> Optional[dict[str, Any]]: |
| """Symbol or mint -> {symbol, mint, decimals}, from Jupiter. |
| |
| Built-ins short-circuit. Everything else must come back from Jupiter |
| with an integer `decimals` — an absent or non-integer value is a |
| refusal, not a default, for the reason in the module docstring. |
| """ |
| for symbol, meta in SOLANA_TOKENS.items(): |
| if query.upper() == symbol.upper() or query == meta["mint"]: |
| return {"symbol": symbol, "mint": meta["mint"], |
| "decimals": meta["decimals"], "source": "built-in"} |
|
|
| for template in _TOKEN_LOOKUP_URLS: |
| try: |
| r = await client.get(template.format(q=query), headers=JUPITER_HEADERS, timeout=20.0) |
| r.raise_for_status() |
| body = r.json() |
| except Exception as exc: |
| print(f" {template.split('?')[0]} — {type(exc).__name__}") |
| continue |
| rows = body if isinstance(body, list) else [body] |
| for row in rows: |
| if not isinstance(row, dict): |
| continue |
| mint = row.get("id") or row.get("address") or row.get("mint") |
| decimals = row.get("decimals") |
| symbol = row.get("symbol") or "" |
| if not mint or not isinstance(decimals, int): |
| continue |
| if query == mint or query.upper() == str(symbol).upper(): |
| return {"symbol": symbol or query, "mint": mint, |
| "decimals": decimals, "source": template.split("?")[0]} |
| return None |
|
|
|
|
| async def quote( |
| client: httpx.AsyncClient, in_mint: str, out_mint: str, |
| amount_raw: int, slippage_bps: int, |
| ) -> Optional[int]: |
| params = { |
| "inputMint": in_mint, "outputMint": out_mint, "amount": str(amount_raw), |
| "slippageBps": str(slippage_bps), "onlyDirectRoutes": "false", |
| "swapMode": "ExactIn", |
| } |
| try: |
| r = await client.get(f"{JUPITER_QUOTE_API}/quote", params=params, |
| headers=JUPITER_HEADERS, timeout=15.0) |
| if r.status_code != 200: |
| return None |
| out = r.json().get("outAmount") |
| return int(out) if out else None |
| except Exception: |
| return None |
|
|
|
|
| async def round_trip_bps( |
| client: httpx.AsyncClient, base: dict, mid: dict, |
| size: float, slippage_bps: int, pace: float, |
| ) -> Optional[float]: |
| amount_raw = int(size * (10 ** base["decimals"])) |
| out_mid = await quote(client, base["mint"], mid["mint"], amount_raw, slippage_bps) |
| await asyncio.sleep(pace) |
| if not out_mid: |
| return None |
| back = await quote(client, mid["mint"], base["mint"], out_mid, slippage_bps) |
| await asyncio.sleep(pace) |
| if not back: |
| return None |
| out_units = back / (10 ** base["decimals"]) |
| return ((out_units - size) / size) * 10_000.0 |
|
|
|
|
| async def main() -> int: |
| ap = argparse.ArgumentParser(description=__doc__) |
| ap.add_argument("token", help="symbol or mint address to verify") |
| ap.add_argument("--base", default="USDC", help="base asset (default USDC)") |
| ap.add_argument("--rounds", type=int, default=3, help="repeats per size (default 3)") |
| ap.add_argument("--slippage", type=int, default=50) |
| ap.add_argument("--sizes", default="", help="comma-separated base units, overrides the default ladder") |
| args = ap.parse_args() |
|
|
| base_meta = SOLANA_TOKENS.get(args.base) |
| if not base_meta: |
| print(f"unknown base '{args.base}' — pick one of: {', '.join(SOLANA_TOKENS)}") |
| return 2 |
| base = {"symbol": args.base, **base_meta} |
| sizes = ([float(s) for s in args.sizes.split(",") if s.strip()] |
| if args.sizes else list(_DEFAULT_SIZES)) |
| pace = _pace_secs() |
|
|
| async with httpx.AsyncClient() as client: |
| print(f"\n1/3 resolving '{args.token}'") |
| mid = await resolve_token(client, args.token) |
| if not mid: |
| print(f"\n ✗ could not resolve '{args.token}' to a mint with a known " |
| f"decimals.\n Nothing is added on a guess — a wrong decimals " |
| f"scales every trade\n by a power of ten. Pass the exact mint " |
| f"address if the symbol is ambiguous.\n") |
| return 1 |
| print(f" {mid['symbol']} {mid['mint']}") |
| print(f" decimals {mid['decimals']} (source: {mid['source']})") |
| if mid["mint"] == base["mint"]: |
| print("\n ✗ that is the base asset itself.\n") |
| return 2 |
|
|
| total = len(sizes) * args.rounds |
| print(f"\n2/3 quoting {base['symbol']}->{mid['symbol']}->{base['symbol']} — " |
| f"{len(sizes)} size(s) x {args.rounds} round(s), " |
| f"~{total * 2 * pace / 60:.1f} min\n") |
|
|
| results: dict[float, list[float]] = {} |
| for size in sizes: |
| samples: list[float] = [] |
| for r in range(args.rounds): |
| bps = await round_trip_bps(client, base, mid, size, args.slippage, pace) |
| if bps is None: |
| print(f" {size:>9,.0f} {base['symbol']} round {r+1}: no quote") |
| continue |
| samples.append(bps) |
| mark = "🟢" if bps > 0 else " " |
| print(f" {mark} {size:>9,.0f} {base['symbol']} round {r+1}: {bps:>9.3f} bps") |
| if samples: |
| results[size] = samples |
|
|
| if not results: |
| print("\n ✗ nothing could be priced. Check the Jupiter key and network.\n") |
| return 1 |
|
|
| print("\n3/3 verdict\n") |
| print(f" {'size':>10} {'median':>9} {'min':>9} {'max':>9} {'n':>3}") |
| print(" " + "-" * 48) |
| medians: dict[float, float] = {} |
| for size, samples in results.items(): |
| med = statistics.median(samples) |
| medians[size] = med |
| mark = "🟢" if med > 0 else " " |
| print(f" {mark} {size:>10,.0f} {med:>9.3f} {min(samples):>9.3f} " |
| f"{max(samples):>9.3f} {len(samples):>3}") |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| try: |
| from modules.size_optimizer import fit_curve |
|
|
| fit = fit_curve([(s, s * m / 10_000.0) for s, m in medians.items()]) |
| if fit and fit.r2 >= 0.5: |
| |
| |
| |
| per_10k = fit.beta * 10_000.0 * 10_000.0 |
| print(f"\n impact: every extra 10,000 {base['symbol']} costs " |
| f"{per_10k:.3f} bps (fit R²={fit.r2:.2f})") |
| if fit.beta > 0: |
| zero_at = fit.alpha / fit.beta |
| if zero_at > 0: |
| print(f" the round trip crosses zero around " |
| f"{zero_at:,.0f} {base['symbol']}" |
| + ("" if zero_at <= max(medians) |
| else " — extrapolated past what was measured, " |
| "treat as a direction, not a number")) |
| except Exception: |
| pass |
|
|
| positive = {s: m for s, m in medians.items() if m > 0} |
| print() |
| if not positive: |
| print( |
| " ✗ NOT VERIFIED — the median is negative at every size tested.\n\n" |
| " The survey caught noise, not an edge. Quote-to-quote variation\n" |
| " here is larger than the number being measured, which is exactly\n" |
| " why one observation was never enough to act on. Do not add it.\n" |
| ) |
| return 1 |
|
|
| best_size = max(positive, key=lambda s: positive[s] * s) |
| best_bps = positive[best_size] |
| largest_ok = max(positive) |
| gross_usd = best_size * best_bps / 10_000.0 |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| spread = max(results[best_size]) - min(results[best_size]) |
| n_best = len(results[best_size]) |
| noisy = spread > abs(best_bps) |
|
|
| print(f" ✓ HOLDS at {len(positive)} of {len(medians)} size(s).") |
| if noisy: |
| print(f" ⚠ but the {n_best} quotes at {best_size:,.0f} spanned " |
| f"{spread:.3f} bps around a\n" |
| f" median of {best_bps:+.3f} — the noise is " |
| f"{spread / abs(best_bps):.1f}x the edge. With n={n_best} the\n" |
| f" SIGN of that median is barely established. Re-run with " |
| f"--rounds 9\n before trusting it; if the median flips, " |
| f"there was never an edge.") |
| print(f" Best expected value: {best_bps:+.3f} bps at {best_size:,.0f} " |
| f"{base['symbol']} = ${gross_usd:+.4f} gross per round trip.") |
| if largest_ok < max(medians): |
| print(f" Breaks down above {largest_ok:,.0f} {base['symbol']} — price " |
| f"impact eats it.\n Cap the size; do not let " |
| f"SOLANA_REAL_LOAN_UNITS exceed that.") |
|
|
| floor = float(os.getenv("MIN_PROFIT_FLOOR_USD", "0.20") or 0.20) |
| haircut, source = effective_haircut_bps() |
| print() |
| if haircut >= best_bps: |
| print(f" ⚠ the leg-2 haircut is {haircut:.2f} bps ({source}) and this " |
| f"edge is {best_bps:.2f} bps.\n The haircut is subtracted from " |
| f"every quote, so this route would score\n " |
| f"{best_bps - haircut:+.2f} bps and never once signal." |
| + ("\n That value is LEARNED — it rises only on a real on-chain " |
| "6024 and\n decays back down on its own. Setting " |
| "SOLANA_LEG2_HAIRCUT_BPS will not\n change it while " |
| "HAIRCUT_ADAPTIVE_ENABLED is on." if source == "learned" else |
| "\n Lower the haircut below the edge, or adding the route " |
| "changes nothing.")) |
| if gross_usd < floor: |
| print(f" ⚠ MIN_PROFIT_FLOOR_USD is ${floor:.2f} and the best gross here is " |
| f"${gross_usd:.4f}.\n Nothing clears the floor at any size that " |
| f"still quotes positive.") |
|
|
| if mid["source"] != "built-in": |
| print("\n Add it with:\n") |
| print(f" ./scripts/setenv.sh \\\n" |
| f" SOLANA_EXTRA_TOKENS={mid['symbol']}:{mid['mint']}:{mid['decimals']} \\\n" |
| f" SOLANA_EXTRA_ROUTES={base['symbol']}>{mid['symbol']}") |
| else: |
| print(f"\n {mid['symbol']} is already a configured token. Add the route with:\n") |
| print(f" ./scripts/setenv.sh SOLANA_EXTRA_ROUTES={base['symbol']}>{mid['symbol']}") |
| print("\n Then restart, and read /nearmiss after a few hours — a route that\n" |
| " signals and never lands is losing a race, which is a different\n" |
| " problem from one that never signals.\n") |
| return 0 |
|
|
|
|
| if __name__ == "__main__": |
| raise SystemExit(asyncio.run(main())) |
|
|