From 23083c7aa7903c74dc23a2537da783c4250cf465 Mon Sep 17 00:00:00 2001 From: jaxperro Date: Sun, 19 Jul 2026 16:14:35 -0400 Subject: [PATCH] =?UTF-8?q?dead-code=20sweep:=20retire=20copytrade=20CLI?= =?UTF-8?q?=20+=20LiveExecutor=20(archived=20stack),=20dedupe=20=5Fbook=5F?= =?UTF-8?q?snapshot=E2=86=92book=5Fdepth,=20dead=20keys,=20honest=20docstr?= =?UTF-8?q?ings;=20fly.value.toml=20=E2=86=92=20value/=20(closes=20#6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- copybot.py | 24 +--- copytrade.py | 150 +------------------------ fly.value.toml => value/fly.value.toml | 0 3 files changed, 8 insertions(+), 166 deletions(-) rename fly.value.toml => value/fly.value.toml (100%) diff --git a/copybot.py b/copybot.py index acc8a992..47a9144a 100644 --- a/copybot.py +++ b/copybot.py @@ -62,8 +62,8 @@ import urllib.request from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer from copytrade import ( # the execution engine (sizing, gates, executors) - CopyTrader, PaperExecutor, LiveExecutor, DEFAULT_CONFIG, - load_json, save_json, new_state, recent_trades, confirm_live, clob_price, + CopyTrader, PaperExecutor, DEFAULT_CONFIG, load_json, save_json, + new_state, recent_trades, confirm_live, clob_price, book_depth, ) import smart_money as sm # noqa: E402 from smart_money import SSL_CTX # noqa: E402 @@ -979,25 +979,7 @@ class Copybot: backtest and a depth gate before sizing up. NB captured just AFTER our own fill, so ask-side depth is net of what we took — fine for a first-order model. Best-effort: never blocks or fails a copy.""" - try: - req = urllib.request.Request(f"{CLOB_API}/book?token_id={token}", - headers={"User-Agent": "Mozilla/5.0"}) - b = json.loads(urllib.request.urlopen(req, timeout=6, context=SSL_CTX).read()) - bids = b.get("bids") or [] - asks = b.get("asks") or [] - bb = max((float(x["price"]) for x in bids), default=None) - ba = min((float(x["price"]) for x in asks), default=None) - - def depth(side, ref, sgn): - if ref is None: - return None - return round(sum(float(x["size"]) * float(x["price"]) for x in side - if sgn * (float(x["price"]) - ref) >= -0.05), 2) - return {"bb": bb, "ba": ba, - "spread": round(ba - bb, 4) if bb is not None and ba is not None else None, - "bid5c": depth(bids, bb, 1), "ask5c": depth(asks, ba, -1)} - except Exception: - return None + return book_depth(token) # dedupe 2026-07-19 (closes #6): was a line-for-line copy def _record_lag(self, wallet, t, fill): """Gap 1 — log the detection lag and price slippage of a copy: their fill diff --git a/copytrade.py b/copytrade.py index ed659469..34ec4dcb 100644 --- a/copytrade.py +++ b/copytrade.py @@ -8,25 +8,11 @@ Watches a list of wallets and mirrors their trades onto your own account: - guard: skip a copy if the price rose >5 POINTS above their fill (absolute — 0.14→0.15 follows; better-than-theirs never blocked) -SAFETY ------- -Runs in PAPER mode by default — it logs exactly what it would do and places -nothing. Live trading requires ALL of: - 1. "mode": "live" in the config, - 2. the --live command-line flag, - 3. typing the confirmation phrase when prompted, - 4. py-clob-client installed and valid credentials in the config. -Hard risk caps (per-trade, daily spend, total exposure, open positions, price -bounds) apply in both modes. This is real money in live mode — you are -responsible for the configuration and the outcomes. - -Usage ------ - python3 copytrade.py --init # write config.example.json - python3 copytrade.py # paper mode (safe) - python3 copytrade.py --once # one polling pass, then exit - python3 copytrade.py --live # live mode (requires config + confirm) - python3 copytrade.py --config my.json # custom config path +This is the ENGINE ONLY (the standalone CLI was retired 2026-07-19 — it had +been broken since the 07-10 price_guard rename, proof nobody ran it; closes +#6). copybot.py is the sole runner: it wires the engine to detection, the +executors, and the book. Hard risk caps and the paper-default safety model +live in DEFAULT_CONFIG + confirm_live(). """ import argparse @@ -56,7 +42,6 @@ DEFAULT_CONFIG = { "bankroll_pct": 0.02, # fraction of CURRENT equity per new entry # (compounds up and down; falls back to a flat # fraction of bankroll_usd when cash isn't tracked) - "stake_cap_usd": 0, # >0: pin stakes at this size once the book grows # past cap/bankroll_pct — surplus cash is SWEPT to # state["reserve"], a banked pot that never bets # (profit ratchet + keeps fills inside book depth) @@ -280,48 +265,6 @@ class PaperExecutor: return {"ok": True, "filled_shares": shares, "price": price, "paper": True} -class LiveExecutor: - """Places real orders via py-clob-client. Imported lazily.""" - live = True - - def __init__(self, cfg): - try: - from py_clob_client.client import ClobClient - from py_clob_client.clob_types import OrderArgs, OrderType - from py_clob_client.order_builder.constants import BUY, SELL - except ImportError: - sys.exit("Live mode needs py-clob-client: pip install py-clob-client") - self._OrderArgs, self._OrderType = OrderArgs, OrderType - self._BUY, self._SELL = BUY, SELL - live = cfg["live"] - if not live.get("private_key"): - sys.exit("Live mode needs live.private_key in the config.") - self.client = ClobClient( - host=CLOB_API, - key=live["private_key"], - chain_id=POLYGON_CHAIN_ID, - signature_type=live.get("signature_type", 1), - funder=live.get("funder_address") or None, - ) - self.client.set_api_creds(self.client.create_or_derive_api_creds()) - - def _order(self, token_id, shares, price, side): - args = self._OrderArgs(price=round(price, 3), size=round(shares, 2), - side=side, token_id=token_id) - signed = self.client.create_order(args) - resp = self.client.post_order(signed, self._OrderType.GTC) - ok = bool(resp and resp.get("success", True)) - return {"ok": ok, "filled_shares": shares, "price": price, - "resp": resp, "paper": False} - - def buy(self, token_id, shares, price, meta): - return self._order(token_id, shares, price, self._BUY) - - def sell(self, token_id, shares, price, meta): - return self._order(token_id, shares, price, self._SELL) - - -# ── engine ──────────────────────────────────────────────────────────────── class CopyTrader: def __init__(self, cfg, state, executor, state_path): @@ -752,50 +695,6 @@ class CopyTrader: f"(won't be copied as new entries)") # -- one polling pass over every watched wallet -- - def poll_once(self, first_pass): - started = self.state["started_at"] - for wallet in self.cfg["watchlist"]: - self.seed_wallet(wallet) - trades = recent_trades(wallet) - # oldest-first so position math is causal - for t in sorted(trades, key=lambda x: x.get("timestamp", 0)): - # on the very first pass, ignore anything from before we started - if first_pass and t.get("timestamp", 0) < started: - self.seen.add(t.get("transactionHash")) - continue - self.handle_trade(wallet, t) - self.persist() - - def run(self, once): - mode = "LIVE — REAL MONEY" if self.ex.live else "PAPER (no orders placed)" - self.log(f"copy-trader started · mode: {mode}") - self.log(f"watching {len(self.cfg['watchlist'])} wallets · " - f"bankroll ${self.cfg['bankroll_usd']:.0f} @ " - f"{self.cfg['bankroll_pct']:.1%}/entry · " - f"guard {self.cfg['price_guard_pct']:.0%}") - if self.webhook: - post_discord(self.webhook, - f"✅ **Copy-trade tracker connected** ({mode})\n" - f"watching {len(self.cfg['watchlist'])} wallets · " - f"${self.cfg['bankroll_usd']:.0f} bankroll @ " - f"{self.cfg['bankroll_pct']:.1%}/entry · " - f"guard {self.cfg['price_guard_pct']:.0%}\n" - f"You'll get a ping on every trade it would place.") - if not self.cfg["watchlist"]: - self.log("watchlist is empty — add wallets to the config. " - "(Run smart_money.py to find them.)") - return - first = True - try: - while True: - self.poll_once(first_pass=first) - first = False - if once: - break - time.sleep(self.cfg["poll_seconds"]) - except KeyboardInterrupt: - self.log("stopped.") - # ── cli ────────────────────────────────────────────────────────────────── @@ -825,42 +724,3 @@ def confirm_live(cfg): sys.exit("Aborted — not confirmed.") -def main(): - ap = argparse.ArgumentParser(description=__doc__, - formatter_class=argparse.RawDescriptionHelpFormatter) - ap.add_argument("--config", default="config.json") - ap.add_argument("--state", default=STATE_PATH_DEFAULT) - ap.add_argument("--live", action="store_true", - help="enable live trading (also needs mode:live in config)") - ap.add_argument("--once", action="store_true", help="one pass, then exit") - ap.add_argument("--init", action="store_true", - help="write config.example.json and exit") - args = ap.parse_args() - - if args.init: - save_json("config.example.json", DEFAULT_CONFIG) - print("Wrote config.example.json — copy to config.json and edit.") - return - - if not os.path.exists(args.config): - sys.exit(f"No config at {args.config}. Run --init to create a template.") - cfg = {**DEFAULT_CONFIG, **load_json(args.config, {})} - cfg["risk"] = {**DEFAULT_CONFIG["risk"], **cfg.get("risk", {})} - cfg["live"] = {**DEFAULT_CONFIG["live"], **cfg.get("live", {})} - - want_live = args.live and cfg.get("mode") == "live" - if args.live and cfg.get("mode") != "live": - sys.exit('--live given but config "mode" is not "live". Refusing to trade.') - - state = load_json(args.state, new_state()) - if want_live: - confirm_live(cfg) - executor = LiveExecutor(cfg) - else: - executor = PaperExecutor() - - CopyTrader(cfg, state, executor, args.state).run(once=args.once) - - -if __name__ == "__main__": - main() diff --git a/fly.value.toml b/value/fly.value.toml similarity index 100% rename from fly.value.toml rename to value/fly.value.toml