copybot: their-bet stake ceiling replaces stake cap + banked reserve (parity with backtest)

stake = class % of equity (drawdown-braked), capped at the signal's own
position size so far (OPEN caps at their fill, ADDs grow with their
position). No sweep, no $250 pin — fills stay within size the market
demonstrably absorbed from the signal itself.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
jaxperro
2026-07-05 23:45:56 -04:00
parent 1b8f48eda3
commit 6d2fb0f34b
2 changed files with 65 additions and 42 deletions
+23 -31
View File
@@ -280,39 +280,31 @@ class CopyTrader:
pct = pcts.get(self.wallet_class(wallet))
return self.cfg["bankroll_pct"] if pct is None else float(pct)
def stake_usd(self, wallet=None):
def stake_usd(self, wallet=None, their=None):
"""Next bet size = the wallet's class fraction (stake_frac) × current
WORKING equity (cash + open cost basis), so stakes compound with the book
in both directions; halved while in a >20% drawdown from the high-water
mark. Falls back to the flat static stake when cash isn't tracked
(legacy poll CLI). The sweep threshold below stays on the BASE
bankroll_pct so which wallet happens to trade doesn't change when
profits get banked.
stake_cap_usd (profit ratchet): once working equity exceeds
cap/bankroll_pct — the level where stakes hit the cap — the surplus CASH
is swept into state["reserve"]: banked, never bet, immune to drawdowns.
Stakes stay pinned ~at the cap, where marketable fills are still inside
typical book depth."""
WORKING equity (cash + open cost basis), halved in a >20% drawdown from
the high-water mark — and NEVER larger than the followed wallet's own
stake (`their` = the signal's position size so far): when the
percentage works out to more than they actually bet, mirror their
exact amount. The stake cap and banked-reserve sweep are retired
(2026-07-06, with the backtest's banking logic) — the their-bet
ceiling is the liquidity bound now: fills stay within size the market
demonstrably absorbed from the signal itself. Falls back to the flat
static stake when cash isn't tracked (legacy poll CLI)."""
frac = self.stake_frac(wallet)
cash = self.state.get("cash")
if cash is None:
return self.cfg["bankroll_usd"] * frac
base = self.cfg["bankroll_pct"]
cap = self.cfg.get("stake_cap_usd") or 0
eq = cash + self.open_exposure()
if cap and base > 0 and eq > cap / base:
sweep = min(cash, eq - cap / base)
if sweep > 0:
self.state["cash"] = cash = cash - sweep
self.state["reserve"] = self.state.get("reserve", 0.0) + sweep
eq -= sweep
hwm = max(self.state.get("hwm", 0.0), eq)
self.state["hwm"] = hwm
if eq < self.DD_THRESHOLD * hwm:
frac *= self.DD_FACTOR
stake = frac * eq
return min(stake, cap) if cap else stake
stake = self.cfg["bankroll_usd"] * frac
else:
eq = cash + self.open_exposure()
hwm = max(self.state.get("hwm", 0.0), eq)
self.state["hwm"] = hwm
if eq < self.DD_THRESHOLD * hwm:
frac *= self.DD_FACTOR
stake = frac * eq
if their and stake > their:
stake = their
return stake
def record_miss(self, wallet, token, cond, title, outcome, price, want, reason):
"""A bet the strategy WOULD have copied but the book couldn't take —
@@ -453,7 +445,7 @@ class CopyTrader:
their_prev = self.state["their_pos"].get(wallet, {}).get(token, 0)
frac = their_size / their_prev if their_prev > 0 else 0
want_shares = mine["shares"] * frac
room = self.stake_usd(wallet) - mine["cost"]
room = self.stake_usd(wallet, their_prev + their_size) - mine["cost"]
if room < self.risk["min_order_usd"]:
self.log(f"ADD {label} — skip (position ${mine['cost']:.0f} already "
f"at the stake size)")
@@ -461,7 +453,7 @@ class CopyTrader:
want_usd = min(want_shares * price, room)
kind = "ADD "
else:
want_usd = self.stake_usd(wallet) # fraction of current equity (compounds)
want_usd = self.stake_usd(wallet, their_prev + their_size) # class % of equity, capped at their bet
kind = "OPEN"
pos_cost = mine["cost"] if is_add else 0.0
+42 -11
View File
@@ -4,14 +4,46 @@
"bankroll_pct": 0.04,
"price_guard_pct": 0.05,
"wallets": [
{ "wallet": "0xe8ca3f758c93f44f3ec210542ab78afb7c0bcccb", "name": "Kruto2027", "class": "volume" },
{ "wallet": "0x86c878cde72660ec52f5e6f0f0438b76de8fc867", "name": "fortuneking", "class": "volume" },
{ "wallet": "0x41558102a796ba971c7567cad41c307e59f8fa41", "name": "LSB1", "class": "volume" },
{ "wallet": "0xd96750bf8d941a8186e592b0ae6e096da66aa266", "name": "imwalkinghere", "class": "volume" },
{ "wallet": "0xfc81760d44a21acc9fd4b749a5bf9a9b2eeae072", "name": "iohihoo", "class": "volume" },
{ "wallet": "0xf5fe759cece500f58a431ef8dacea321f6e3e23d", "name": "Stavenson", "class": "whale" },
{ "wallet": "0x73afc8160c17830c0c7281a7bf570c871455b880", "name": "0x4bFb-whale", "class": "whale" },
{ "wallet": "0x72e1597864456eda62878413cf3e60c332e4a45d", "name": "ArbTrader", "class": "whale" }
{
"wallet": "0xe8ca3f758c93f44f3ec210542ab78afb7c0bcccb",
"name": "Kruto2027",
"class": "volume"
},
{
"wallet": "0x86c878cde72660ec52f5e6f0f0438b76de8fc867",
"name": "fortuneking",
"class": "volume"
},
{
"wallet": "0x41558102a796ba971c7567cad41c307e59f8fa41",
"name": "LSB1",
"class": "volume"
},
{
"wallet": "0xd96750bf8d941a8186e592b0ae6e096da66aa266",
"name": "imwalkinghere",
"class": "volume"
},
{
"wallet": "0xfc81760d44a21acc9fd4b749a5bf9a9b2eeae072",
"name": "iohihoo",
"class": "volume"
},
{
"wallet": "0xf5fe759cece500f58a431ef8dacea321f6e3e23d",
"name": "Stavenson",
"class": "whale"
},
{
"wallet": "0x73afc8160c17830c0c7281a7bf570c871455b880",
"name": "0x4bFb-whale",
"class": "whale"
},
{
"wallet": "0x72e1597864456eda62878413cf3e60c332e4a45d",
"name": "ArbTrader",
"class": "whale"
}
],
"follow": {
"buy_only": true,
@@ -33,6 +65,5 @@
"max_price": 0.99,
"min_order_usd": 5.0,
"max_per_event": 0
},
"stake_cap_usd": 250.0
}
}
}