dead-code sweep: retire copytrade CLI + LiveExecutor (archived stack), dedupe _book_snapshot→book_depth, dead keys, honest docstrings; fly.value.toml → value/ (closes #6)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
jaxperro
2026-07-19 16:14:35 -04:00
parent b310e28284
commit 23083c7aa7
3 changed files with 8 additions and 166 deletions
+3 -21
View File
@@ -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
+5 -145
View File
@@ -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.140.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()