band-guarded mirror exits (#21 successor): mirror sells >=90c only, sub-band sells ignored+ledgered; sell-band study slice; backtest parity

Sell-band evidence: every band <90c LOSES by mirroring (<30c +$79/sell
given up, 50-70c +$7.29); >=90c SAVES (-$0.85/sell, n=68).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
jaxperro
2026-07-23 18:34:19 -04:00
parent a534766ede
commit 54f4c511ab
5 changed files with 222 additions and 157 deletions
+91 -84
View File
@@ -1,89 +1,96 @@
{
"mode": "live",
"_comment": "Live-test config, SECRET-FREE and committed. The Fly live worker (wwf-copybot-live, COPYBOT_ROLE=live) reads THIS file and takes secrets from env (LIVE_PRIVATE_KEY/LIVE_FUNDER_ADDRESS + LIVE_CONFIRM to arm). Mac use: cp -> config.live.json and fill live.*. RULES: bankroll_usd MUST equal actual equity at rebase time (the 1.4 cash-anchor checks state cash vs chain). 2026-07-10 USER decision: hard caps retired \u2014 sizing is paper-parity 4%-of-equity (floored at the venue $1 min order); follow set mirrors copybot.paper.json (Set E) so paper and live books stay comparable.",
"bankroll_usd": 66.42,
"bankroll_pct": 0.04,
"price_guard_abs": 0.05,
"fak_retry_s": 10,
"fak_retry_niche_s": {"crypto": 4, "esports": 10, "sports": 25,
"geo": 25, "politics": 25, "other": 25},
"taker_fee_rate": 0.03,
"discord_webhook": "",
"follow": {
"buy_only": true,
"entry_mode": "maker",
"exit_mode": "hold",
"min_their_usd": 25.0,
"min_entry": 0.0,
"max_entry": 0.95,
"class_pct": {
"volume": 0.04,
"whale": 0.12
}
"mode": "live",
"_comment": "Live-test config, SECRET-FREE and committed. The Fly live worker (wwf-copybot-live, COPYBOT_ROLE=live) reads THIS file and takes secrets from env (LIVE_PRIVATE_KEY/LIVE_FUNDER_ADDRESS + LIVE_CONFIRM to arm). Mac use: cp -> config.live.json and fill live.*. RULES: bankroll_usd MUST equal actual equity at rebase time (the 1.4 cash-anchor checks state cash vs chain). 2026-07-10 USER decision: hard caps retired \u2014 sizing is paper-parity 4%-of-equity (floored at the venue $1 min order); follow set mirrors copybot.paper.json (Set E) so paper and live books stay comparable.",
"bankroll_usd": 66.42,
"bankroll_pct": 0.04,
"price_guard_abs": 0.05,
"fak_retry_s": 10,
"fak_retry_niche_s": {
"crypto": 4,
"esports": 10,
"sports": 25,
"geo": 25,
"politics": 25,
"other": 25
},
"taker_fee_rate": 0.03,
"discord_webhook": "",
"follow": {
"buy_only": true,
"entry_mode": "maker",
"exit_mode": "mirror",
"min_their_usd": 25.0,
"min_entry": 0.0,
"max_entry": 0.95,
"class_pct": {
"volume": 0.04,
"whale": 0.12
},
"risk": {
"max_trade_usd": 1000000.0,
"max_position_usd": 1000000.0,
"daily_spend_cap_usd": 1000000.0,
"max_total_exposure_usd": 1000000.0,
"max_open_positions": 1000,
"max_per_event": 0,
"min_price": 0.0,
"max_price": 0.99,
"min_order_usd": 1.0
"mirror_sell_min_p": 0.9
},
"risk": {
"max_trade_usd": 1000000.0,
"max_position_usd": 1000000.0,
"daily_spend_cap_usd": 1000000.0,
"max_total_exposure_usd": 1000000.0,
"max_open_positions": 1000,
"max_per_event": 0,
"min_price": 0.0,
"max_price": 0.99,
"min_order_usd": 1.0
},
"live": {
"private_key": "",
"funder_address": "",
"signature_type": 1,
"order_type": "FAK",
"auto_redeem": false,
"rpc_url": ""
},
"wallets": [
{
"wallet": "0xe8ca3f758c93f44f3ec210542ab78afb7c0bcccb",
"name": "Kruto2027",
"class": "volume",
"floor": 80.0
},
"live": {
"private_key": "",
"funder_address": "",
"signature_type": 1,
"order_type": "FAK",
"auto_redeem": false,
"rpc_url": ""
{
"wallet": "0xbadaf319415c17f28824a43ae0cd912b9d84d874",
"name": "0xbadaf319",
"class": "volume",
"floor": 41.21
},
"wallets": [
{
"wallet": "0xe8ca3f758c93f44f3ec210542ab78afb7c0bcccb",
"name": "Kruto2027",
"class": "volume",
"floor": 80.0
},
{
"wallet": "0xbadaf319415c17f28824a43ae0cd912b9d84d874",
"name": "0xbadaf319",
"class": "volume",
"floor": 41.21
},
{
"wallet": "0xd7d36345c4aab150e59577e360696b01d01d698b",
"name": "gkmgkldfmg",
"class": "volume",
"floor": 470.51
},
{
"wallet": "0xa1d57d329227c75b12b09f927fb3d6d6ef8f1343",
"name": "1kto1m",
"class": "volume",
"floor": 328.77
},
{
"wallet": "0x215adbb63b47d0ca92f849fe2c2dc1adb0f6254c",
"name": "BikesAreTheBikes",
"class": "volume",
"floor": 227.2
},
{
"wallet": "0x921433c93558b9a4ba807ec824d02aad7ea2ddbf",
"name": "AIcAIc",
"class": "volume",
"floor": 341.23
},
{
"wallet": "0x40ce68f1564f3c751b12d88a393d8cc0651dbf90",
"name": "JuiceFarm",
"class": "volume"
}
],
"feed_path": "live/copybot_live_real.json",
"fill_log": "copybot_fills.live.jsonl",
"shadow_log": "rtds_shadow.live.jsonl"
{
"wallet": "0xd7d36345c4aab150e59577e360696b01d01d698b",
"name": "gkmgkldfmg",
"class": "volume",
"floor": 470.51
},
{
"wallet": "0xa1d57d329227c75b12b09f927fb3d6d6ef8f1343",
"name": "1kto1m",
"class": "volume",
"floor": 328.77
},
{
"wallet": "0x215adbb63b47d0ca92f849fe2c2dc1adb0f6254c",
"name": "BikesAreTheBikes",
"class": "volume",
"floor": 227.2
},
{
"wallet": "0x921433c93558b9a4ba807ec824d02aad7ea2ddbf",
"name": "AIcAIc",
"class": "volume",
"floor": 341.23
},
{
"wallet": "0x40ce68f1564f3c751b12d88a393d8cc0651dbf90",
"name": "JuiceFarm",
"class": "volume"
}
],
"feed_path": "live/copybot_live_real.json",
"fill_log": "copybot_fills.live.jsonl",
"shadow_log": "rtds_shadow.live.jsonl"
}
+23
View File
@@ -732,6 +732,29 @@ class CopyTrader:
price = self._live_price(token, "sell")
if price is None:
return
# band-guarded mirroring (2026-07-23, #21 successor): the sell-band
# study (research/sell_mirror_study.py) showed mirrored exits LOSE
# in every band below 90c (exits gave up value; <30c +$79/sell left
# on the table) and SAVE money at >=90c (near-resolution
# profit-taking, n=68 -$0.85/sell). Sells below the floor are
# ignored-and-ledgered exactly like hold mode, so the
# counterfactual stream keeps accruing.
min_p = (self.cfg.get("follow") or {}).get("mirror_sell_min_p", 0.0)
if price < min_p:
self.log(f"EXIT {label} — BAND-SKIP (sell {price:.2f} < "
f"{min_p:.2f} floor, their frac {frac:.2f})")
if self.on_ignored_exit:
self.on_ignored_exit({
"ts": round(time.time(), 1), "token": str(token),
"their_size": their_size, "their_prev": their_prev,
"frac": round(frac, 4), "reason": "band",
"sell_px": round(price, 4),
"shares_held": round(mine.get("shares", 0), 4),
"cost_held": round(mine.get("cost", 0), 2),
"title": (mine.get("title") or "")[:80],
"outcome": mine.get("outcome"),
"wallet": mine.get("wallet", "")})
return
if any(po.get("token") == token
for po in self.state.get("pending_orders", [])):
self.log(f"EXIT {label} — skip (in-play hold already pending on "
+70 -69
View File
@@ -1,73 +1,74 @@
{
"mode": "paper",
"bankroll_usd": 1000.0,
"bankroll_pct": 0.04,
"wallets": [
{
"wallet": "0xe8ca3f758c93f44f3ec210542ab78afb7c0bcccb",
"name": "Kruto2027",
"class": "volume",
"floor": 80.0,
"floor_pin": 80.0
},
{
"wallet": "0xbadaf319415c17f28824a43ae0cd912b9d84d874",
"name": "0xbadaf319",
"class": "volume",
"floor": 30.71
},
{
"wallet": "0xd7d36345c4aab150e59577e360696b01d01d698b",
"name": "gkmgkldfmg",
"class": "volume",
"floor": 470.51
},
{
"wallet": "0xa1d57d329227c75b12b09f927fb3d6d6ef8f1343",
"name": "1kto1m",
"class": "volume",
"floor": 314.43
},
{
"wallet": "0x215adbb63b47d0ca92f849fe2c2dc1adb0f6254c",
"name": "BikesAreTheBikes",
"class": "volume",
"floor": 219.66
},
{
"wallet": "0x921433c93558b9a4ba807ec824d02aad7ea2ddbf",
"name": "AIcAIc",
"class": "volume",
"floor": 375.06
},
{
"wallet": "0x40ce68f1564f3c751b12d88a393d8cc0651dbf90",
"name": "JuiceFarm",
"class": "volume"
}
],
"follow": {
"buy_only": true,
"entry_mode": "maker",
"exit_mode": "hold",
"min_their_usd": 25.0,
"min_entry": 0.0,
"max_entry": 0.95,
"class_pct": {
"volume": 0.04,
"whale": 0.12
}
"mode": "paper",
"bankroll_usd": 1000.0,
"bankroll_pct": 0.04,
"wallets": [
{
"wallet": "0xe8ca3f758c93f44f3ec210542ab78afb7c0bcccb",
"name": "Kruto2027",
"class": "volume",
"floor": 80.0,
"floor_pin": 80.0
},
"risk": {
"max_trade_usd": 1000000.0,
"max_position_usd": 1000000.0,
"daily_spend_cap_usd": 1000000.0,
"max_total_exposure_usd": 1000000.0,
"max_open_positions": 1000,
"min_price": 0.0,
"max_price": 0.99,
"min_order_usd": 5.0,
"max_per_event": 0
{
"wallet": "0xbadaf319415c17f28824a43ae0cd912b9d84d874",
"name": "0xbadaf319",
"class": "volume",
"floor": 30.71
},
"price_guard_abs": 0.05
{
"wallet": "0xd7d36345c4aab150e59577e360696b01d01d698b",
"name": "gkmgkldfmg",
"class": "volume",
"floor": 470.51
},
{
"wallet": "0xa1d57d329227c75b12b09f927fb3d6d6ef8f1343",
"name": "1kto1m",
"class": "volume",
"floor": 314.43
},
{
"wallet": "0x215adbb63b47d0ca92f849fe2c2dc1adb0f6254c",
"name": "BikesAreTheBikes",
"class": "volume",
"floor": 219.66
},
{
"wallet": "0x921433c93558b9a4ba807ec824d02aad7ea2ddbf",
"name": "AIcAIc",
"class": "volume",
"floor": 375.06
},
{
"wallet": "0x40ce68f1564f3c751b12d88a393d8cc0651dbf90",
"name": "JuiceFarm",
"class": "volume"
}
],
"follow": {
"buy_only": true,
"entry_mode": "maker",
"exit_mode": "mirror",
"min_their_usd": 25.0,
"min_entry": 0.0,
"max_entry": 0.95,
"class_pct": {
"volume": 0.04,
"whale": 0.12
},
"mirror_sell_min_p": 0.9
},
"risk": {
"max_trade_usd": 1000000.0,
"max_position_usd": 1000000.0,
"daily_spend_cap_usd": 1000000.0,
"max_total_exposure_usd": 1000000.0,
"max_open_positions": 1000,
"min_price": 0.0,
"max_price": 0.99,
"min_order_usd": 5.0,
"max_per_event": 0
},
"price_guard_abs": 0.05
}
+16 -4
View File
@@ -148,6 +148,17 @@ except Exception:
_FOLLOW = {}
ENTRY_MODE = os.environ.get("BT_ENTRY_MODE", _FOLLOW.get("entry_mode", "taker"))
EXIT_MODE = os.environ.get("BT_EXIT_MODE", _FOLLOW.get("exit_mode", "mirror"))
# band-guarded mirroring (#21 successor): sells below this price are NOT
# mirrored — held to truth like hold mode (sell-band study: every band
# <90c lost by mirroring; >=90c saved). 0.0 = mirror everything.
MIRROR_SELL_MIN_P = float(os.environ.get(
"BT_MIRROR_SELL_MIN_P", _FOLLOW.get("mirror_sell_min_p", 0.0)))
def _mirrors(exit_p):
"""Would the bot mirror a sell at this price under the current flags?"""
return (EXIT_MODE != "hold"
and (exit_p or 0) >= MIRROR_SELL_MIN_P)
def entry_model(p, stake):
@@ -525,7 +536,7 @@ def main():
cash -= cost; fees_paid += fee; perW[b["wallet"]]["bets"] += 1
shares = stake / p_eff # lag-adjusted entry price
if b["kind"] == "res":
if b.get("exit_t") and EXIT_MODE != "hold":
if b.get("exit_t") and _mirrors(b.get("exit_p")):
# the signal SOLD pre-resolution -> mirror the exit, like the
# live bot: their exit price with the slippage haircut against
# us, minus the taker fee (sells pay it; redeems don't)
@@ -581,7 +592,7 @@ def main():
stake = m.get("stake") or STAKE_MIN
p_eff, fee, cost = entry_model(m["p"], stake)
shares = stake / p_eff
if m.get("exit_t") and EXIT_MODE != "hold": # would have mirrored
if m.get("exit_t") and _mirrors(m.get("exit_p")): # would have mirrored
xp = max(0.001, m["exit_p"] * (1 - SLIP))
return shares * xp - shares * FEE_RATE * xp * (1 - xp) - cost
if m.get("wp") is not None or m.get("won") is not None:
@@ -600,12 +611,12 @@ def main():
return m.get("won")
def _missed_won(m):
if m.get("exit_t") and EXIT_MODE != "hold":
if m.get("exit_t") and _mirrors(m.get("exit_p")):
return None # mirrored exit: truth is the price
return _truth_won(m)
def _missed_status(m):
if m.get("exit_t") and EXIT_MODE != "hold":
if m.get("exit_t") and _mirrors(m.get("exit_p")):
return "sold"
w = _truth_won(m)
if w is None:
@@ -683,6 +694,7 @@ def main():
for m in missed[:60]],
"missed_pnl": round(sum(hypo_pnl(m) for m in missed), 2),
"entry_mode": ENTRY_MODE, "exit_mode": EXIT_MODE,
"mirror_sell_min_p": MIRROR_SELL_MIN_P,
}
json.dump(out, open(os.path.join(HERE, OUT) if not os.path.isabs(OUT) else OUT, "w"),
separators=(",", ":"))
+22
View File
@@ -37,6 +37,28 @@ def main():
db = tape.connect()
tape.build_resolved(db)
pays = fwd.payouts_for(db, [str(s["token"]) for s in sells])
# sell-price band slice: WHERE is mirroring unprofitable? delta>0 = the
# exit gave up value (holding won); delta<0 = the exit saved us.
BANDS = [(0, .30, "<30c"), (.30, .50, "30-50c"), (.50, .70, "50-70c"),
(.70, .90, "70-90c"), (.90, 1.01, ">=90c")]
print("\n== SELL-PRICE BANDS (both books pooled, chain-graded) ==")
for lo, hi, tag in BANDS:
bn = bd = 0
for s2 in sells:
p2 = pays.get(str(s2["token"]))
if p2 is None or p2 == 0.5:
continue
sp = s2.get("price") or 0
if not (lo <= sp < hi):
continue
sh2 = s2.get("shares") or 0
bn += 1
bd += (p2 - sp) * sh2
if bn:
lbl = ("SKIP-MIRROR (exits gave up value)" if bd > 0
else "mirror OK (exits saved money)")
print(f" {tag:>7}: n={bn:>3} · net delta {bd:+8.2f} "
f"(${bd/bn:+.2f}/sell) -> {lbl}")
for book in ("live", "paper"):
ss = [s for s in sells if s["_book"] == book]
n = unk = refund = right = wrong = 0