diff --git a/.gitignore b/.gitignore index 12474f4f..8273636d 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,4 @@ live/scored.json live/*_scored.json live/watch_prejune*.json live/history/ +live/slug_cache.json diff --git a/archive/copytrade.py b/archive/copytrade.py index 512529ad..2aea69e5 100644 --- a/archive/copytrade.py +++ b/archive/copytrade.py @@ -30,6 +30,7 @@ Usage import argparse import json import os +import re import sys import time import urllib.error @@ -49,8 +50,10 @@ DEFAULT_CONFIG = { "poll_seconds": 12, # how often to check each wallet "discord_webhook": "", # paste a Discord webhook URL to get pings "watchlist": [], # ["0xwallet1", "0xwallet2", ...] - "bankroll_usd": 1000.0, # your stake pool - "bankroll_pct": 0.02, # 2% of bankroll per new entry + "bankroll_usd": 1000.0, # starting stake pool + "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) "price_guard_pct": 0.05, # skip if price moved >5% from their fill "risk": { "max_trade_usd": 50.0, # hard ceiling on any single copy @@ -58,6 +61,8 @@ DEFAULT_CONFIG = { "daily_spend_cap_usd": 250.0, "max_total_exposure_usd": 500.0, "max_open_positions": 20, + "max_per_event": 2, # max concurrent positions on one real-world + # event (a game's markets are one correlated bet) "min_price": 0.05, # don't open longshots/near-certainties "max_price": 0.95, "min_order_usd": 5.0, # Polymarket min order size @@ -155,6 +160,16 @@ def recent_trades(wallet, limit=100): {"user": wallet, "type": "TRADE", "limit": limit}) or [] +def event_key(t): + """Correlation-group id for a trade: the real-world event its market belongs + to. Polymarket sub-splits one game across several eventSlugs + (`…-2026-07-01-more-markets`, `…-2026-07-01-second-half-result`), so dated + slugs collapse to their `…-YYYY-MM-DD` prefix; undated slugs stand as-is.""" + ev = t.get("eventSlug") or t.get("slug") or "" + m = re.match(r"(.*?\d{4}-\d{2}-\d{2})", ev) + return m.group(1) if m else (ev or None) + + # ── execution ──────────────────────────────────────────────────────────────── class PaperExecutor: @@ -243,6 +258,26 @@ class CopyTrader: def open_exposure(self): return sum(p["cost"] for p in self.state["my_pos"].values()) + # ---- dynamic sizing: fraction of CURRENT equity, with a drawdown brake ---- + DD_THRESHOLD = 0.80 # below 80% of the high-water mark… + DD_FACTOR = 0.5 # …bet half size until equity recovers + + def stake_usd(self): + """Next bet size = bankroll_pct × current 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).""" + cash = self.state.get("cash") + if cash is None: + return self.cfg["bankroll_usd"] * self.cfg["bankroll_pct"] + eq = cash + self.open_exposure() + hwm = max(self.state.get("hwm", 0.0), eq) + self.state["hwm"] = hwm + frac = self.cfg["bankroll_pct"] + if eq < self.DD_THRESHOLD * hwm: + frac *= self.DD_FACTOR + return frac * eq + def persist(self): self.state["seen_tx"] = list(self.seen)[-5000:] save_json(self.state_path, self.state) @@ -295,7 +330,7 @@ class CopyTrader: if side == "BUY": self._handle_their_buy(wallet, token, their_size, their_price, - label, title, outcome) + label, title, outcome, event=event_key(t)) their_book[token] = their_prev + their_size elif side == "SELL": self._handle_their_sell(token, their_size, their_prev, label) @@ -317,7 +352,7 @@ class CopyTrader: return drift <= self.cfg["price_guard_pct"] def _handle_their_buy(self, wallet, token, their_size, their_price, - label, title, outcome): + label, title, outcome, event=None): mine = self.state["my_pos"].get(token) is_add = mine is not None # don't backfill: never open a position they already held when we @@ -326,6 +361,17 @@ class CopyTrader: if not is_add and token in self.state["seed_tokens"].get(wallet, []): self.log(f"BUY {label} — skip (held before we started, no backfill)") return + # correlation cap: a game's markets settle together — N bets on one event + # are one big bet, not N diversified ones (LSB1 once stacked 6 markets on + # a single match). Cap concurrent positions per real-world event. + cap = self.risk.get("max_per_event") + if not is_add and event and cap: + held = sum(1 for p in self.state["my_pos"].values() + if p.get("event") == event) + if held >= cap: + self.log(f"BUY {label} — skip (already {held} positions on this " + f"event, cap {cap})") + return price = self._live_price(token, "buy") if price is None: @@ -343,7 +389,7 @@ class CopyTrader: want_usd = want_shares * price kind = "ADD " else: - want_usd = self.cfg["bankroll_usd"] * self.cfg["bankroll_pct"] + want_usd = self.stake_usd() # fraction of current equity (compounds) kind = "OPEN" pos_cost = mine["cost"] if is_add else 0.0 @@ -364,7 +410,7 @@ class CopyTrader: else: self.state["my_pos"][token] = { "shares": res["filled_shares"], "cost": spent, - "title": title, "outcome": outcome} + "title": title, "outcome": outcome, "event": event} tag = "[PAPER]" if not self.ex.live else "[LIVE]" self.alert( f"{kind} {label} — {tag} buy {res['filled_shares']:.1f} " diff --git a/copybot.py b/copybot.py index 187d0986..526c52ed 100644 --- a/copybot.py +++ b/copybot.py @@ -377,7 +377,10 @@ class Copybot: lag = st.get("lag", {}) feed = { "mode": "live" if self.engine.ex.live else "paper", - "bankroll": bank, "stake": round(bank * self.cfg["bankroll_pct"], 2), + "bankroll": bank, "stake": round(self.engine.stake_usd(), 2), + "stake_pct": self.cfg["bankroll_pct"], + "event_cap": self.engine.risk.get("max_per_event"), + "hwm": round(st.get("hwm", 0.0), 2), "cash": round(cash, 2), "deployed": round(exp, 2), "realized": round(cash + exp - bank, 2), "open_count": len(mp), "fees_paid": round(st.get("fees_paid", 0.0), 2), @@ -468,7 +471,7 @@ class Copybot: def summary(self, cycle): bank = self.cfg["bankroll_usd"] - stake = bank * self.cfg["bankroll_pct"] + stake = self.engine.stake_usd() # dynamic: pct of current equity exp = self.engine.open_exposure() cash = self.engine.state.get("cash", bank) realized = cash + exp - bank # see _drain_fills / settle_resolved diff --git a/live/copybot.paper.json b/live/copybot.paper.json index 42c3739c..97a0e0f5 100644 --- a/live/copybot.paper.json +++ b/live/copybot.paper.json @@ -1,7 +1,7 @@ { "mode": "paper", "bankroll_usd": 1000.0, - "bankroll_pct": 0.05, + "bankroll_pct": 0.04, "price_guard_pct": 0.05, "watchlist": [ "0xe8ca3f758c93f44f3ec210542ab78afb7c0bcccb", @@ -40,13 +40,14 @@ "max_entry": 1.0 }, "risk": { - "max_trade_usd": 50.0, - "max_position_usd": 50.0, + "max_trade_usd": 150.0, + "max_position_usd": 150.0, "daily_spend_cap_usd": 1000000.0, "max_total_exposure_usd": 1000000.0, "max_open_positions": 1000, "min_price": 0.01, "max_price": 0.99, - "min_order_usd": 5.0 + "min_order_usd": 5.0, + "max_per_event": 2 } } \ No newline at end of file diff --git a/live/portfolio.json b/live/portfolio.json index 8b175be6..20c1e21e 100644 --- a/live/portfolio.json +++ b/live/portfolio.json @@ -1 +1 @@ -{"started":1780286400.0,"updated":1783000990.979182,"bank":1000.0,"stake":50.0,"fee_rate":0.03,"slip":0.005,"lag_est_s":90,"fees_paid":74.27,"equity":2684.69,"liquid":2434.53,"invested":250.16,"realized":1637.19,"pnl":1684.69,"unreal":47.51,"resolved_count":197,"wins":169,"losses":28,"open_count":4,"missed_count":62,"wallets":[{"name":"Kruto2027","wallet":"0xe8ca3f758c93f44f3ec210542ab78afb7c0bcccb","bets":21,"invested":182.12,"realized":320.23,"conv_thr":123},{"name":"shisan888","wallet":"0xf3488e52ac2d7f0628b04481db5a5b0446f0e543","bets":46,"invested":0.0,"realized":505.47,"conv_thr":727},{"name":"fortuneking","wallet":"0x86c878cde72660ec52f5e6f0f0438b76de8fc867","bets":35,"invested":68.04,"realized":147.88,"conv_thr":990},{"name":"LSB1","wallet":"0x41558102a796ba971c7567cad41c307e59f8fa41","bets":99,"invested":0.0,"realized":663.61,"conv_thr":231}],"current":[{"title":"Washington Spirit vs. Houston Dash: Washington Spirit 2nd Half O/U 0.5","name":"Kruto2027","outcome":"Over","stake":50.0,"val":77.94,"pnl":27.12,"end":"2026-07-04"},{"title":"Washington Spirit vs. Houston Dash: 2nd Half O/U 1.5","name":"Kruto2027","outcome":"Over","stake":50.0,"val":65.17,"pnl":14.25,"end":"2026-07-04"},{"title":"ITF Skopje: Nikola Kolyachev vs Vladyslav Orlov","name":"fortuneking","outcome":"Vladyslav Orlov","stake":50.0,"val":68.04,"pnl":17.57,"end":"2026-07-08"},{"title":"Spread: Club ABB (-1.5)","name":"Kruto2027","outcome":"Bamin Real Potos\u00ed","stake":50.0,"val":39.01,"pnl":-11.43,"end":"2026-05-14"}],"resolved":[{"title":"Parma: Laslo Djere vs Chun-Hsin Tseng","name":"fortuneking","won":false,"stake":50.0,"pnl":-50.89,"date":1782360000},{"title":"ICC T20 World Cup, Women: Australia vs Bangladesh","name":"Kruto2027","won":false,"stake":50.0,"pnl":-51.28,"date":1782273600},{"title":"Royan: Leo Raquillet vs Pavel Kotov","name":"Kruto2027","won":false,"stake":50.0,"pnl":-51.39,"date":1782273600},{"title":"Mees Rottgering vs. Kyrian Jacquet: Total Sets O/U 2.5","name":"Kruto2027","won":true,"stake":50.0,"pnl":46.82,"date":1782273600},{"title":"Bellucci vs. Bublik: Match O/U 21.5","name":"Kruto2027","won":true,"stake":50.0,"pnl":22.16,"date":1782187200},{"title":"Egypt to score first vs. New Zealand?","name":"LSB1","won":true,"stake":50.0,"pnl":8.41,"date":1782108000},{"title":"Exact Score: New Zealand 2 - 2 Egypt?","name":"LSB1","won":true,"stake":50.0,"pnl":2.3,"date":1782108000},{"title":"Exact Score: New Zealand 1 - 2 Egypt?","name":"LSB1","won":true,"stake":50.0,"pnl":9.69,"date":1782108000},{"title":"Argentina vs. Austria: 1st Half O/U 0.5","name":"LSB1","won":true,"stake":50.0,"pnl":25.36,"date":1782108000},{"title":"Will France win on 2026-06-22?","name":"LSB1","won":true,"stake":50.0,"pnl":3.4,"date":1782108000},{"title":"Halle Open: Nuno Borges vs Felix Auger-Aliassime","name":"fortuneking","won":true,"stake":50.0,"pnl":2.88,"date":1782100800},{"title":"Nottingham 2: Felix Gill vs Hugo Gaston","name":"fortuneking","won":false,"stake":50.0,"pnl":-51.04,"date":1782100800},{"title":"ITF Tauste: Belle Thompson vs Celia Anson Sanchez","name":"fortuneking","won":false,"stake":50.0,"pnl":-50.42,"date":1782100800},{"title":"New Zealand vs. Egypt: O/U 7.5 Total Corners","name":"shisan888","won":true,"stake":50.0,"pnl":10.16,"date":1782100800},{"title":"New Zealand vs. Egypt: O/U 6.5 Total Corners","name":"shisan888","won":true,"stake":50.0,"pnl":21.66,"date":1782100800},{"title":"Argentina vs. Austria: O/U 6.5 Total Corners","name":"shisan888","won":true,"stake":50.0,"pnl":9.27,"date":1782100800},{"title":"Will Japan win on 2026-06-21?","name":"LSB1","won":true,"stake":50.0,"pnl":13.73,"date":1782021600},{"title":"Exact Score: Tunisia 0 - 1 Japan?","name":"LSB1","won":true,"stake":50.0,"pnl":4.14,"date":1782021600},{"title":"Exact Score: Tunisia 1 - 2 Japan?","name":"LSB1","won":true,"stake":50.0,"pnl":4.39,"date":1782021600},{"title":"Tunisia vs. Japan: 2nd Half O/U 0.5","name":"LSB1","won":true,"stake":50.0,"pnl":10.0,"date":1782021600},{"title":"Exact Score: Any Other Score?","name":"LSB1","won":true,"stake":50.0,"pnl":54.18,"date":1782021600},{"title":"Exact Score: Tunisia 0 - 3 Japan?","name":"LSB1","won":true,"stake":50.0,"pnl":10.54,"date":1782021600},{"title":"Exact Score: Belgium 2 - 0 IR Iran?","name":"LSB1","won":true,"stake":50.0,"pnl":6.36,"date":1782021600},{"title":"Exact Score: Uruguay 0 - 2 Cabo Verde?","name":"LSB1","won":true,"stake":50.0,"pnl":2.06,"date":1782021600},{"title":"Exact Score: Uruguay 3 - 1 Cabo Verde?","name":"LSB1","won":true,"stake":50.0,"pnl":19.64,"date":1782021600},{"title":"Ecuador vs. Cura\u00e7ao: O/U 9.5 Total Corners","name":"shisan888","won":true,"stake":50.0,"pnl":26.63,"date":1782014400},{"title":"Ecuador vs. Cura\u00e7ao: O/U 7.5 Total Corners","name":"shisan888","won":false,"stake":50.0,"pnl":-50.22,"date":1782014400},{"title":"Ecuador vs. Cura\u00e7ao: O/U 6.5 Total Corners","name":"shisan888","won":false,"stake":50.0,"pnl":-50.41,"date":1782014400},{"title":"Ecuador vs. Cura\u00e7ao: O/U 8.5 Total Corners","name":"shisan888","won":false,"stake":50.0,"pnl":-50.4,"date":1782014400},{"title":"Tunisia vs. Japan: O/U 9.5 Total Corners","name":"shisan888","won":true,"stake":50.0,"pnl":41.38,"date":1782014400},{"title":"Tunisia vs. Japan: O/U 8.5 Total Corners","name":"shisan888","won":true,"stake":50.0,"pnl":12.23,"date":1782014400},{"title":"Tunisia vs. Japan: 1st Half O/U 4.5 Total Corners","name":"shisan888","won":true,"stake":50.0,"pnl":0.1,"date":1782014400},{"title":"Spain vs. Saudi Arabia: O/U 7.5 Total Corners","name":"shisan888","won":true,"stake":50.0,"pnl":9.61,"date":1782014400},{"title":"Belgium vs. IR Iran: O/U 9.5 Total Corners","name":"shisan888","won":false,"stake":50.0,"pnl":-50.81,"date":1782014400},{"title":"Uruguay vs. Cabo Verde: O/U 9.5 Total Corners","name":"shisan888","won":true,"stake":50.0,"pnl":0.53,"date":1782014400},{"title":"Exact Score: Brazil 0 - 0 Haiti?","name":"LSB1","won":true,"stake":50.0,"pnl":1.95,"date":1781935200},{"title":"Haiti to score first vs. Brazil?","name":"LSB1","won":true,"stake":50.0,"pnl":3.4,"date":1781935200},{"title":"Brazil vs. Haiti: Brazil 1st Half O/U 1.5","name":"LSB1","won":true,"stake":50.0,"pnl":31.2,"date":1781935200},{"title":"Exact Score: Brazil 3 - 1 Haiti?","name":"LSB1","won":true,"stake":50.0,"pnl":3.0,"date":1781935200},{"title":"Brazil vs. Haiti: 2nd Half O/U 0.5","name":"LSB1","won":true,"stake":50.0,"pnl":1.25,"date":1781935200},{"title":"Netherlands leading at halftime?","name":"LSB1","won":true,"stake":50.0,"pnl":4.54,"date":1781935200},{"title":"Exact Score: Netherlands 2 - 1 Sweden?","name":"LSB1","won":true,"stake":50.0,"pnl":7.65,"date":1781935200},{"title":"Exact Score: Any Other Score?","name":"LSB1","won":true,"stake":50.0,"pnl":22.1,"date":1781935200},{"title":"Will Germany win on 2026-06-20?","name":"LSB1","won":true,"stake":50.0,"pnl":16.85,"date":1781935200},{"title":"Germany to score first vs. C\u00f4te d'Ivoire?","name":"LSB1","won":true,"stake":50.0,"pnl":8.72,"date":1781935200},{"title":"UD Almer\u00eda vs. M\u00e1laga CF: M\u00e1laga CF O/U 0.5","name":"LSB1","won":true,"stake":50.0,"pnl":9.72,"date":1781935200},{"title":"Exact Score: Germany 0 - 1 C\u00f4te d'Ivoire?","name":"LSB1","won":true,"stake":50.0,"pnl":14.35,"date":1781935200},{"title":"Exact Score: Germany 1 - 1 C\u00f4te d'Ivoire?","name":"LSB1","won":true,"stake":50.0,"pnl":8.99,"date":1781935200},{"title":"Brazil vs. Haiti: O/U 10.5 Total Corners","name":"shisan888","won":true,"stake":50.0,"pnl":34.97,"date":1781928000},{"title":"Brazil vs. Haiti: O/U 9.5 Total Corners","name":"shisan888","won":true,"stake":50.0,"pnl":18.7,"date":1781928000},{"title":"Brazil vs. Haiti: O/U 11.5 Total Corners","name":"shisan888","won":true,"stake":50.0,"pnl":4.54,"date":1781928000},{"title":"Brazil vs. Haiti: 1st Half O/U 4.5 Total Corners","name":"shisan888","won":true,"stake":50.0,"pnl":0.2,"date":1781928000},{"title":"Brazil vs. Haiti: O/U 7.5 Total Corners","name":"shisan888","won":true,"stake":50.0,"pnl":20.71,"date":1781928000},{"title":"T\u00fcrkiye vs. Paraguay: O/U 10.5 Total Corners","name":"shisan888","won":true,"stake":50.0,"pnl":34.49,"date":1781928000},{"title":"T\u00fcrkiye vs. Paraguay: O/U 11.5 Total Corners","name":"shisan888","won":true,"stake":50.0,"pnl":34.07,"date":1781928000},{"title":"Netherlands vs. Sweden: O/U 7.5 Total Corners","name":"shisan888","won":true,"stake":50.0,"pnl":41.73,"date":1781928000},{"title":"Netherlands vs. Sweden: O/U 6.5 Total Corners","name":"shisan888","won":true,"stake":50.0,"pnl":9.69,"date":1781928000},{"title":"Germany vs. C\u00f4te d'Ivoire: O/U 9.5 Total Corners","name":"shisan888","won":true,"stake":50.0,"pnl":43.94,"date":1781928000},{"title":"Germany vs. C\u00f4te d'Ivoire: O/U 8.5 Total Corners","name":"shisan888","won":true,"stake":50.0,"pnl":48.11,"date":1781928000},{"title":"Germany vs. C\u00f4te d'Ivoire: O/U 10.5 Total Corners","name":"shisan888","won":true,"stake":50.0,"pnl":77.48,"date":1781928000}],"missed":[{"title":"Tucuman: Federico Coria vs Andrea Collarini","name":"fortuneking","won":true,"stake":50.0,"pnl":36.64,"date":1781755200},{"title":"HSBC Championships: Harriet Dart vs Kamilla Rakhimova","name":"fortuneking","won":false,"stake":50.0,"pnl":-50.85,"date":1781755200},{"title":"ITF Merzig: Sonja Zhenikhova vs Leonie Schuknecht","name":"Kruto2027","won":false,"stake":50.0,"pnl":-50.58,"date":1781668800},{"title":"Exact Score: IR Iran 2 - 3 New Zealand?","name":"LSB1","won":true,"stake":50.0,"pnl":0.85,"date":1781589600},{"title":"IR Iran vs. New Zealand: Both Teams to Score in First Half","name":"Kruto2027","won":false,"stake":50.0,"pnl":-50.42,"date":1781582400},{"title":"Sweden vs. Tunisia: Sweden O/U 2.5","name":"LSB1","won":true,"stake":50.0,"pnl":21.64,"date":1781503200},{"title":"Exact Score: Australia 1 - 1 T\u00fcrkiye?","name":"LSB1","won":true,"stake":50.0,"pnl":13.71,"date":1781416800},{"title":"Haiti vs. Scotland: Haiti O/U 1.5","name":"Kruto2027","won":true,"stake":50.0,"pnl":101.2,"date":1781409600},{"title":"Netherlands vs. Japan: Japan O/U 2.5","name":"Kruto2027","won":true,"stake":50.0,"pnl":44.96,"date":1781409600},{"title":"Netherlands vs. Japan: Japan O/U 1.5","name":"Kruto2027","won":false,"stake":50.0,"pnl":-51.09,"date":1781409600},{"title":"Australia vs. T\u00fcrkiye: Australia O/U 2.5","name":"Kruto2027","won":true,"stake":50.0,"pnl":36.28,"date":1781409600},{"title":"Australia vs. T\u00fcrkiye: Australia O/U 1.5","name":"Kruto2027","won":false,"stake":50.0,"pnl":-51.06,"date":1781409600},{"title":"Folarin Balogun: 1+ goals","name":"LSB1","won":true,"stake":50.0,"pnl":95.34,"date":1781330400},{"title":"Exact Score: United States 2 - 0 Paraguay?","name":"LSB1","won":true,"stake":50.0,"pnl":8.18,"date":1781330400},{"title":"Exact Score: United States 3 - 0 Paraguay?","name":"LSB1","won":true,"stake":50.0,"pnl":2.47,"date":1781330400},{"title":"Libema Open, Qualification: Alexander Maarten Jong vs Shintaro Mochizuki","name":"fortuneking","won":false,"stake":50.0,"pnl":-50.26,"date":1781323200},{"title":"Will Harlequins win?","name":"Kruto2027","won":true,"stake":50.0,"pnl":1.38,"date":1781323200},{"title":"HSBC Championships, Qualification: Elsa Jacquemot vs Hanyu Guo","name":"fortuneking","won":true,"stake":50.0,"pnl":64.85,"date":1781323200},{"title":"Qatar vs. Switzerland: Qatar O/U 1.5","name":"Kruto2027","won":true,"stake":50.0,"pnl":154.85,"date":1781323200},{"title":"Brazil vs. Morocco: Morocco O/U 2.5","name":"Kruto2027","won":true,"stake":50.0,"pnl":38.94,"date":1781323200},{"title":"Brazil vs. Morocco: Morocco O/U 1.5","name":"Kruto2027","won":true,"stake":50.0,"pnl":118.54,"date":1781323200},{"title":"Qatar vs. Switzerland: 1st Half O/U 1.5","name":"Kruto2027","won":true,"stake":50.0,"pnl":48.76,"date":1781323200},{"title":"Exact Score: Korea Republic 2 - 0 Czechia?","name":"LSB1","won":true,"stake":50.0,"pnl":2.51,"date":1781244000},{"title":"Exact Score: Korea Republic 1 - 0 Czechia?","name":"LSB1","won":true,"stake":50.0,"pnl":9.17,"date":1781244000},{"title":"Will CR Brasil win on 2026-06-12?","name":"LSB1","won":false,"stake":50.0,"pnl":-50.25,"date":1781244000},{"title":"Mensik vs. Zverev: Match O/U 38.5","name":"fortuneking","won":true,"stake":50.0,"pnl":48.76,"date":1781236800},{"title":"Mexico vs. South Africa: Mexico O/U 1.5","name":"Kruto2027","won":false,"stake":50.0,"pnl":-51.01,"date":1781150400},{"title":"Mexico vs. South Africa: South Africa O/U 2.5","name":"Kruto2027","won":true,"stake":50.0,"pnl":35.15,"date":1781150400},{"title":"Mexico vs. South Africa: South Africa O/U 1.5","name":"Kruto2027","won":true,"stake":50.0,"pnl":102.07,"date":1781150400},{"title":"Bolivia vs. Algeria: Bolivia O/U 2.5","name":"Kruto2027","won":true,"stake":50.0,"pnl":40.72,"date":1781150400},{"title":"Iraq vs. Venezuela: Iraq O/U 1.5","name":"Kruto2027","won":true,"stake":50.0,"pnl":67.45,"date":1781064000},{"title":"Portugal vs. Nigeria: Portugal O/U 1.5","name":"Kruto2027","won":false,"stake":50.0,"pnl":-51.22,"date":1781064000},{"title":"Will Bahrain vs. Syria end in a draw?","name":"Kruto2027","won":true,"stake":50.0,"pnl":7.91,"date":1780977600},{"title":"Map 3 Rounds Handicap: G2 (-3.5) vs FUT Esports (+3.5)","name":"Kruto2027","won":true,"stake":50.0,"pnl":44.96,"date":1780891200},{"title":"Denmark vs. Ukraine: O/U 3.5","name":"LSB1","won":true,"stake":50.0,"pnl":1.65,"date":1780812000},{"title":"Will Denmark win on 2026-06-07?","name":"LSB1","won":true,"stake":50.0,"pnl":1.82,"date":1780812000},{"title":"Kosovo leading at halftime?","name":"LSB1","won":true,"stake":50.0,"pnl":10.84,"date":1780812000},{"title":"Exact Score: Kosovo 2 - 0 Andorra?","name":"LSB1","won":true,"stake":50.0,"pnl":5.74,"date":1780812000},{"title":"Exact Score: Croatia 2 - 0 Slovenia?","name":"LSB1","won":true,"stake":50.0,"pnl":2.65,"date":1780812000},{"title":"Morocco vs. Norway: O/U 2.5","name":"LSB1","won":true,"stake":50.0,"pnl":3.32,"date":1780812000},{"title":"Exact Score: Jordan 0 - 1 Colombia?","name":"LSB1","won":true,"stake":50.0,"pnl":2.3,"date":1780812000},{"title":"Map 3 Total Rounds: Over/Under 18.5","name":"Kruto2027","won":true,"stake":50.0,"pnl":38.19,"date":1780804800},{"title":"Will J\u00fabilo Iwata win on 2026-06-06?","name":"LSB1","won":true,"stake":50.0,"pnl":2.84,"date":1780725600},{"title":"Portugal vs. Chile: O/U 1.5","name":"LSB1","won":true,"stake":50.0,"pnl":5.75,"date":1780725600},{"title":"Yokohama FC vs. Renofa Yamaguchi FC: O/U 4.5","name":"Kruto2027","won":true,"stake":50.0,"pnl":17.3,"date":1780718400},{"title":"Map Handicap: CHAOS (-1.5) vs WRAITH PCIFIC (+1.5)","name":"Kruto2027","won":true,"stake":50.0,"pnl":95.71,"date":1780718400},{"title":"Counter-Strike: CHAOS vs WRAITH PCIFIC (BO3) - United21 Group D","name":"Kruto2027","won":false,"stake":50.0,"pnl":-50.4,"date":1780718400},{"title":"FC Mito Holly Hock vs. V-Varen Nagasaki: O/U 0.5","name":"Kruto2027","won":true,"stake":50.0,"pnl":12.67,"date":1780718400},{"title":"Will Australia vs. Switzerland end in a draw?","name":"fortuneking","won":false,"stake":50.0,"pnl":-50.25,"date":1780718400},{"title":"Exact Score: Portugal 2 - 1 Chile?","name":"fortuneking","won":true,"stake":50.0,"pnl":16.85,"date":1780718400},{"title":"Cabo Verde vs. Bermuda: O/U 4.5","name":"fortuneking","won":true,"stake":50.0,"pnl":58.12,"date":1780718400},{"title":"Spread: England (-2.5)","name":"fortuneking","won":true,"stake":50.0,"pnl":33.71,"date":1780718400},{"title":"Spread: England (-1.5)","name":"fortuneking","won":true,"stake":50.0,"pnl":114.79,"date":1780718400},{"title":"Spread: T\u00fcrkiye (-1.5)","name":"fortuneking","won":true,"stake":50.0,"pnl":24.88,"date":1780718400},{"title":"Mexico vs. Serbia: O/U 5.5","name":"LSB1","won":true,"stake":50.0,"pnl":142.97,"date":1780639200},{"title":"Georgia vs. Bahrain: O/U 0.5","name":"LSB1","won":true,"stake":50.0,"pnl":5.14,"date":1780639200},{"title":"Georgia leading at halftime?","name":"LSB1","won":false,"stake":50.0,"pnl":-50.62,"date":1780639200},{"title":"San Marino vs. Bangladesh: O/U 0.5","name":"LSB1","won":true,"stake":50.0,"pnl":5.78,"date":1780639200},{"title":"Moldova vs. Bulgaria: O/U 0.5","name":"LSB1","won":true,"stake":50.0,"pnl":9.23,"date":1780639200},{"title":"Exact Score: Georgia 3 - 0 Bahrain?","name":"LSB1","won":true,"stake":50.0,"pnl":4.48,"date":1780639200}],"missed_pnl":1112.94} \ No newline at end of file +{"started":1780286400.0,"updated":1783003918.6936169,"bank":1000.0,"stake":150.0,"stake_pct":0.04,"event_cap":2,"hwm":5514.03,"dd_threshold":0.8,"capped_count":26,"fee_rate":0.03,"slip":0.005,"lag_est_s":90,"fees_paid":151.98,"equity":5255.5,"liquid":5068.69,"invested":186.8,"realized":4230.92,"pnl":4255.5,"unreal":24.57,"resolved_count":214,"wins":181,"losses":33,"open_count":4,"missed_count":45,"wallets":[{"name":"Kruto2027","wallet":"0xe8ca3f758c93f44f3ec210542ab78afb7c0bcccb","bets":32,"invested":150.04,"realized":939.82,"conv_thr":123},{"name":"shisan888","wallet":"0xf3488e52ac2d7f0628b04481db5a5b0446f0e543","bets":42,"invested":0.0,"realized":1390.25,"conv_thr":727},{"name":"fortuneking","wallet":"0x86c878cde72660ec52f5e6f0f0438b76de8fc867","bets":44,"invested":36.77,"realized":388.11,"conv_thr":990},{"name":"LSB1","wallet":"0x41558102a796ba971c7567cad41c307e59f8fa41","bets":100,"invested":0.0,"realized":1512.74,"conv_thr":231}],"current":[{"title":"Washington Spirit vs. Houston Dash: Washington Spirit 2nd Half O/U 0.5","name":"Kruto2027","outcome":"Over","stake":40.0,"val":67.22,"pnl":26.56,"end":"2026-07-04"},{"title":"Washington Spirit vs. Houston Dash: 2nd Half O/U 1.5","name":"Kruto2027","outcome":"Over","stake":40.0,"val":51.62,"pnl":10.88,"end":"2026-07-04"},{"title":"ITF Ajaccio: Robin Catry vs Clement Lemire","name":"fortuneking","outcome":"Robin Catry","stake":40.0,"val":36.77,"pnl":-3.72,"end":"2026-07-09"},{"title":"Spread: Club ABB (-1.5)","name":"Kruto2027","outcome":"Bamin Real Potos\u00ed","stake":40.0,"val":31.2,"pnl":-9.14,"end":"2026-05-14"}],"resolved":[{"title":"Parma: Laslo Djere vs Chun-Hsin Tseng","name":"fortuneking","won":false,"stake":139.04,"pnl":-141.51,"date":1782360000},{"title":"ICC T20 World Cup, Women: Australia vs Bangladesh","name":"Kruto2027","won":false,"stake":86.12,"pnl":-88.33,"date":1782273600},{"title":"Royan: Leo Raquillet vs Pavel Kotov","name":"Kruto2027","won":false,"stake":129.55,"pnl":-133.14,"date":1782273600},{"title":"Mees Rottgering vs. Kyrian Jacquet: Total Sets O/U 2.5","name":"Kruto2027","won":true,"stake":129.55,"pnl":121.31,"date":1782273600},{"title":"Bellucci vs. Bublik: Match O/U 21.5","name":"Kruto2027","won":true,"stake":95.62,"pnl":42.38,"date":1782187200},{"title":"Egypt to score first vs. New Zealand?","name":"LSB1","won":true,"stake":150.0,"pnl":25.23,"date":1782108000},{"title":"Argentina vs. Austria: 1st Half O/U 0.5","name":"LSB1","won":true,"stake":150.0,"pnl":76.09,"date":1782108000},{"title":"Will France win on 2026-06-22?","name":"LSB1","won":true,"stake":150.0,"pnl":10.19,"date":1782108000},{"title":"Halle Open: Nuno Borges vs Felix Auger-Aliassime","name":"fortuneking","won":true,"stake":86.12,"pnl":4.96,"date":1782100800},{"title":"","name":"fortuneking","won":false,"stake":94.61,"pnl":-96.57,"date":1782100800},{"title":"ITF Tauste: Belle Thompson vs Celia Anson Sanchez","name":"fortuneking","won":false,"stake":94.61,"pnl":-95.4,"date":1782100800},{"title":"New Zealand vs. Egypt: O/U 7.5 Total Corners","name":"shisan888","won":true,"stake":150.0,"pnl":30.49,"date":1782100800},{"title":"Argentina vs. Austria: O/U 6.5 Total Corners","name":"shisan888","won":true,"stake":150.0,"pnl":27.81,"date":1782100800},{"title":"Will Japan win on 2026-06-21?","name":"LSB1","won":true,"stake":150.0,"pnl":41.2,"date":1782021600},{"title":"Exact Score: Tunisia 0 - 1 Japan?","name":"LSB1","won":true,"stake":150.0,"pnl":12.41,"date":1782021600},{"title":"","name":"LSB1","won":true,"stake":150.0,"pnl":13.17,"date":1782021600},{"title":"Exact Score: Belgium 2 - 0 IR Iran?","name":"LSB1","won":true,"stake":150.0,"pnl":19.08,"date":1782021600},{"title":"Exact Score: Uruguay 0 - 2 Cabo Verde?","name":"LSB1","won":true,"stake":150.0,"pnl":6.18,"date":1782021600},{"title":"Exact Score: Uruguay 3 - 1 Cabo Verde?","name":"LSB1","won":true,"stake":150.0,"pnl":58.93,"date":1782021600},{"title":"Ecuador vs. Cura\u00e7ao: O/U 9.5 Total Corners","name":"shisan888","won":true,"stake":150.0,"pnl":79.89,"date":1782014400},{"title":"Ecuador vs. Cura\u00e7ao: O/U 7.5 Total Corners","name":"shisan888","won":false,"stake":150.0,"pnl":-150.67,"date":1782014400},{"title":"Tunisia vs. Japan: O/U 9.5 Total Corners","name":"shisan888","won":true,"stake":150.0,"pnl":124.15,"date":1782014400},{"title":"Tunisia vs. Japan: O/U 8.5 Total Corners","name":"shisan888","won":true,"stake":150.0,"pnl":36.7,"date":1782014400},{"title":"Tunisia vs. Japan: 1st Half O/U 4.5 Total Corners","name":"shisan888","won":true,"stake":150.0,"pnl":0.3,"date":1782014400},{"title":"Spain vs. Saudi Arabia: O/U 7.5 Total Corners","name":"shisan888","won":true,"stake":150.0,"pnl":28.82,"date":1782014400},{"title":"Belgium vs. IR Iran: O/U 9.5 Total Corners","name":"shisan888","won":false,"stake":150.0,"pnl":-152.42,"date":1782014400},{"title":"Uruguay vs. Cabo Verde: O/U 9.5 Total Corners","name":"shisan888","won":true,"stake":150.0,"pnl":1.6,"date":1782014400},{"title":"Netherlands leading at halftime?","name":"LSB1","won":true,"stake":150.0,"pnl":13.63,"date":1781935200},{"title":"Exact Score: Netherlands 2 - 1 Sweden?","name":"LSB1","won":true,"stake":150.0,"pnl":22.94,"date":1781935200},{"title":"Exact Score: Any Other Score?","name":"LSB1","won":true,"stake":150.0,"pnl":66.3,"date":1781935200},{"title":"Will Germany win on 2026-06-20?","name":"LSB1","won":true,"stake":150.0,"pnl":50.54,"date":1781935200},{"title":"Germany to score first vs. C\u00f4te d'Ivoire?","name":"LSB1","won":true,"stake":150.0,"pnl":26.17,"date":1781935200},{"title":"UD Almer\u00eda vs. M\u00e1laga CF: M\u00e1laga CF O/U 0.5","name":"LSB1","won":true,"stake":150.0,"pnl":29.15,"date":1781935200},{"title":"Exact Score: Germany 0 - 1 C\u00f4te d'Ivoire?","name":"LSB1","won":true,"stake":150.0,"pnl":43.04,"date":1781935200},{"title":"Exact Score: Germany 1 - 1 C\u00f4te d'Ivoire?","name":"LSB1","won":true,"stake":150.0,"pnl":26.98,"date":1781935200},{"title":"Brazil vs. Haiti: O/U 10.5 Total Corners","name":"shisan888","won":true,"stake":150.0,"pnl":104.9,"date":1781928000},{"title":"Brazil vs. Haiti: O/U 9.5 Total Corners","name":"shisan888","won":true,"stake":150.0,"pnl":56.09,"date":1781928000},{"title":"T\u00fcrkiye vs. Paraguay: O/U 10.5 Total Corners","name":"shisan888","won":true,"stake":150.0,"pnl":103.47,"date":1781928000},{"title":"T\u00fcrkiye vs. Paraguay: O/U 11.5 Total Corners","name":"shisan888","won":true,"stake":150.0,"pnl":102.21,"date":1781928000},{"title":"Netherlands vs. Sweden: O/U 7.5 Total Corners","name":"shisan888","won":true,"stake":150.0,"pnl":125.18,"date":1781928000},{"title":"Netherlands vs. Sweden: O/U 6.5 Total Corners","name":"shisan888","won":true,"stake":150.0,"pnl":29.06,"date":1781928000},{"title":"Germany vs. C\u00f4te d'Ivoire: O/U 9.5 Total Corners","name":"shisan888","won":true,"stake":150.0,"pnl":131.81,"date":1781928000},{"title":"Germany vs. C\u00f4te d'Ivoire: O/U 8.5 Total Corners","name":"shisan888","won":true,"stake":150.0,"pnl":144.34,"date":1781928000},{"title":"Germany vs. C\u00f4te d'Ivoire: O/U 10.5 Total Corners","name":"shisan888","won":true,"stake":150.0,"pnl":232.43,"date":1781928000},{"title":"","name":"LSB1","won":true,"stake":150.0,"pnl":21.14,"date":1781848800},{"title":"Exact Score: United States 0 - 0 Australia?","name":"LSB1","won":true,"stake":150.0,"pnl":6.91,"date":1781848800},{"title":"Exact Score: United States 1 - 0 Australia?","name":"LSB1","won":true,"stake":150.0,"pnl":17.13,"date":1781848800},{"title":"Exact Score: Scotland 0 - 0 Morocco?","name":"LSB1","won":true,"stake":150.0,"pnl":8.8,"date":1781848800},{"title":"Exact Score: Scotland 1 - 0 Morocco?","name":"LSB1","won":true,"stake":150.0,"pnl":6.91,"date":1781848800},{"title":"Ismael Saibari: 1+ goals","name":"LSB1","won":true,"stake":150.0,"pnl":33.43,"date":1781848800},{"title":"Mexico vs. Korea Republic: O/U 6.5 Total Corners","name":"shisan888","won":true,"stake":150.0,"pnl":56.81,"date":1781841600},{"title":"United States vs. Australia: O/U 11.5 Total Corners","name":"shisan888","won":true,"stake":150.0,"pnl":87.47,"date":1781841600},{"title":"United States vs. Australia: O/U 8.5 Total Corners","name":"shisan888","won":true,"stake":150.0,"pnl":23.96,"date":1781841600},{"title":"Scotland vs. Morocco: O/U 6.5 Total Corners","name":"shisan888","won":true,"stake":150.0,"pnl":3.88,"date":1781841600},{"title":"Colombia leading at halftime?","name":"LSB1","won":true,"stake":135.27,"pnl":104.94,"date":1781762400},{"title":"Will Colombia win on 2026-06-17?","name":"LSB1","won":true,"stake":135.27,"pnl":37.48,"date":1781762400},{"title":"Will Czechia vs. South Africa end in a draw?","name":"LSB1","won":true,"stake":139.04,"pnl":86.15,"date":1781762400},{"title":"Exact Score: Canada 0 - 0 Qatar?","name":"LSB1","won":true,"stake":144.38,"pnl":5.12,"date":1781762400},{"title":"Exact Score: Canada 1 - 0 Qatar?","name":"LSB1","won":true,"stake":148.75,"pnl":11.79,"date":1781762400},{"title":"Canada vs. Qatar: 1st Half O/U 1.5","name":"LSB1","won":true,"stake":149.22,"pnl":22.88,"date":1781762400}],"missed":[{"title":"Exact Score: New Zealand 2 - 2 Egypt?","name":"LSB1","won":true,"stake":150.0,"capped":true,"pnl":6.91,"date":1782108000},{"title":"Exact Score: New Zealand 1 - 2 Egypt?","name":"LSB1","won":true,"stake":150.0,"capped":true,"pnl":29.08,"date":1782108000},{"title":"New Zealand vs. Egypt: O/U 6.5 Total Corners","name":"shisan888","won":true,"stake":150.0,"capped":true,"pnl":64.97,"date":1782100800},{"title":"Tunisia vs. Japan: 2nd Half O/U 0.5","name":"LSB1","won":true,"stake":150.0,"capped":true,"pnl":30.0,"date":1782021600},{"title":"Exact Score: Any Other Score?","name":"LSB1","won":true,"stake":150.0,"capped":true,"pnl":162.53,"date":1782021600},{"title":"Exact Score: Tunisia 0 - 3 Japan?","name":"LSB1","won":true,"stake":150.0,"capped":true,"pnl":31.63,"date":1782021600},{"title":"Ecuador vs. Cura\u00e7ao: O/U 6.5 Total Corners","name":"shisan888","won":false,"stake":150.0,"capped":true,"pnl":-151.24,"date":1782014400},{"title":"Ecuador vs. Cura\u00e7ao: O/U 8.5 Total Corners","name":"shisan888","won":false,"stake":150.0,"capped":true,"pnl":-151.19,"date":1782014400},{"title":"Exact Score: Brazil 0 - 0 Haiti?","name":"LSB1","won":true,"stake":150.0,"capped":true,"pnl":5.84,"date":1781935200},{"title":"Haiti to score first vs. Brazil?","name":"LSB1","won":true,"stake":150.0,"capped":true,"pnl":10.19,"date":1781935200},{"title":"Brazil vs. Haiti: Brazil 1st Half O/U 1.5","name":"LSB1","won":true,"stake":150.0,"capped":true,"pnl":93.59,"date":1781935200},{"title":"Exact Score: Brazil 3 - 1 Haiti?","name":"LSB1","won":true,"stake":150.0,"capped":true,"pnl":9.0,"date":1781935200},{"title":"Brazil vs. Haiti: 2nd Half O/U 0.5","name":"LSB1","won":true,"stake":150.0,"capped":true,"pnl":3.76,"date":1781935200},{"title":"Brazil vs. Haiti: O/U 11.5 Total Corners","name":"shisan888","won":true,"stake":150.0,"capped":true,"pnl":13.63,"date":1781928000},{"title":"Brazil vs. Haiti: 1st Half O/U 4.5 Total Corners","name":"shisan888","won":true,"stake":150.0,"capped":true,"pnl":0.59,"date":1781928000},{"title":"Brazil vs. Haiti: O/U 7.5 Total Corners","name":"shisan888","won":true,"stake":150.0,"capped":true,"pnl":62.13,"date":1781928000},{"title":"ITF Kayseri: Ustiniya Lekomtseva vs Sofiia Bielinska","name":"Kruto2027","won":true,"stake":59.17,"capped":false,"pnl":23.26,"date":1781755200},{"title":"Set 1 Winner: Arias/Ingildsen vs Forcano/Vervoort","name":"fortuneking","won":true,"stake":58.77,"capped":false,"pnl":4.14,"date":1781755200},{"title":"Tucuman: Federico Coria vs Andrea Collarini","name":"fortuneking","won":true,"stake":58.77,"capped":false,"pnl":43.07,"date":1781755200},{"title":"Exact Score: Argentina 2 - 1 Algeria?","name":"LSB1","won":true,"stake":117.03,"capped":true,"pnl":9.22,"date":1781676000},{"title":"ITF Merzig: Sonja Zhenikhova vs Leonie Schuknecht","name":"Kruto2027","won":false,"stake":61.17,"capped":false,"pnl":-61.88,"date":1781668800},{"title":"Cirstea vs. Raducanu: Set 1 Games O/U 8.5","name":"Kruto2027","won":true,"stake":58.77,"capped":false,"pnl":55.03,"date":1781668800},{"title":"Exact Score: IR Iran 1 - 3 New Zealand?","name":"LSB1","won":true,"stake":94.82,"capped":true,"pnl":10.68,"date":1781589600},{"title":"Exact Score: IR Iran 2 - 3 New Zealand?","name":"LSB1","won":true,"stake":94.82,"capped":true,"pnl":1.62,"date":1781589600},{"title":"","name":"Kruto2027","won":false,"stake":58.77,"capped":false,"pnl":-59.26,"date":1781582400},{"title":"Exact Score: Sweden 3 - 0 Tunisia?","name":"LSB1","won":true,"stake":88.35,"capped":true,"pnl":7.75,"date":1781503200},{"title":"Sweden vs. Tunisia: Sweden O/U 2.5","name":"LSB1","won":true,"stake":88.35,"capped":true,"pnl":38.24,"date":1781503200},{"title":"Haiti to score first vs. Scotland?","name":"LSB1","won":true,"stake":79.45,"capped":true,"pnl":22.83,"date":1781416800},{"title":"Exact Score: Australia 1 - 1 T\u00fcrkiye?","name":"LSB1","won":true,"stake":86.57,"capped":true,"pnl":23.74,"date":1781416800},{"title":"Netherlands vs. Japan: Japan O/U 2.5","name":"Kruto2027","won":true,"stake":61.17,"capped":false,"pnl":55.0,"date":1781409600},{"title":"Netherlands vs. Japan: Japan O/U 1.5","name":"Kruto2027","won":false,"stake":61.17,"capped":false,"pnl":-62.51,"date":1781409600},{"title":"Australia vs. T\u00fcrkiye: Australia O/U 2.5","name":"Kruto2027","won":true,"stake":61.17,"capped":false,"pnl":44.39,"date":1781409600},{"title":"Australia vs. T\u00fcrkiye: Australia O/U 1.5","name":"Kruto2027","won":false,"stake":61.17,"capped":false,"pnl":-62.47,"date":1781409600},{"title":"Folarin Balogun: 1+ goals","name":"LSB1","won":true,"stake":60.45,"capped":false,"pnl":115.27,"date":1781330400},{"title":"Exact Score: United States 2 - 0 Paraguay?","name":"LSB1","won":true,"stake":60.45,"capped":false,"pnl":9.88,"date":1781330400},{"title":"Exact Score: United States 3 - 0 Paraguay?","name":"LSB1","won":true,"stake":60.45,"capped":false,"pnl":2.99,"date":1781330400},{"title":"Qatar vs. Switzerland: 1st Half O/U 1.5","name":"Kruto2027","won":true,"stake":61.17,"capped":true,"pnl":59.65,"date":1781323200},{"title":"Exact Score: Korea Republic 2 - 0 Czechia?","name":"LSB1","won":true,"stake":58.77,"capped":false,"pnl":2.95,"date":1781244000},{"title":"Exact Score: Korea Republic 1 - 0 Czechia?","name":"LSB1","won":true,"stake":58.77,"capped":false,"pnl":10.78,"date":1781244000},{"title":"Will CR Brasil win on 2026-06-12?","name":"LSB1","won":false,"stake":60.45,"capped":false,"pnl":-60.76,"date":1781244000},{"title":"Mexico vs. South Africa: South Africa O/U 2.5","name":"Kruto2027","won":true,"stake":61.17,"capped":true,"pnl":43.01,"date":1781150400},{"title":"Mexico vs. South Africa: South Africa O/U 1.5","name":"Kruto2027","won":true,"stake":61.17,"capped":true,"pnl":124.87,"date":1781150400},{"title":"Bolivia vs. Algeria: Bolivia O/U 2.5","name":"Kruto2027","won":true,"stake":61.17,"capped":false,"pnl":49.82,"date":1781150400},{"title":"Iraq vs. Venezuela: Iraq O/U 1.5","name":"Kruto2027","won":true,"stake":61.17,"capped":false,"pnl":82.52,"date":1781064000},{"title":"Portugal vs. Nigeria: Portugal O/U 1.5","name":"Kruto2027","won":false,"stake":61.17,"capped":false,"pnl":-62.67,"date":1781064000}],"missed_pnl":692.59} \ No newline at end of file diff --git a/live/portfolio.py b/live/portfolio.py index c8853acc..eacde190 100644 --- a/live/portfolio.py +++ b/live/portfolio.py @@ -10,20 +10,24 @@ RECYCLES correctly (cash frees at the true resolution moment). Output -> portfol which the dashboard reads in one request. Model: a $1,000 account that mirrors each followed wallet's CONVICTION bets (top-20% -stake) at a flat $50, held to resolution (the cache has no sell events, which is the -right model for the hold-to-resolution wallets we follow). Entries pay the Polymarket -taker fee and a lag-slippage price haircut (see FEE_RATE / SLIP / LAG_EST_S) so the -book models what a real copier nets, not the idealized zero-cost mirror. One position -per market (first wallet to enter wins the slot); when capital is fully deployed a bet -is MISSED. +stake), held to resolution (the cache has no sell events, which is the right model for +the hold-to-resolution wallets we follow). Sizing is DYNAMIC — each bet stakes PCT of +current equity (Kelly-style compounding), halved in a >20% drawdown, capped at +EVENT_CAP concurrent bets per real-world event — and entries pay the Polymarket taker +fee plus a lag-slippage price haircut (FEE_RATE / SLIP / LAG_EST_S), so the book +models what a real copier nets, not the idealized zero-cost mirror. One position per +market (first wallet to enter wins the slot); when capital is fully deployed a bet is +MISSED. Resolved history + realized P&L come from the cache; currently-open bets come from a small live /positions pull so the page can still show what's in flight. """ import json import os +import re import ssl import time import urllib.request +from concurrent.futures import ThreadPoolExecutor import cache import smart_money as sm @@ -32,14 +36,25 @@ _SSL = ssl._create_unverified_context() HERE = os.path.dirname(__file__) BANK = 1000.0 -STAKE = 50.0 START = time.mktime(time.strptime("2026-06-01", "%Y-%m-%d")) # backfilled: replay from June 1 GAMMA = "https://gamma-api.polymarket.com" +# ---- dynamic sizing (mirrors the live copybot) ------------------------------ +# Each new bet stakes PCT of CURRENT equity (cash + open cost basis) so the book +# compounds in both directions; the stake is halved while equity sits below +# DD_THRESHOLD of its high-water mark, and clamped to [STAKE_MIN, STAKE_CAP]. +# EVENT_CAP limits concurrent bets whose markets belong to the same real-world +# event — a game's markets settle together, so N bets on one match are one +# correlated bet, not N diversified ones. +PCT = 0.04 +STAKE_MIN, STAKE_CAP = 5.0, 150.0 +EVENT_CAP = 2 +DD_THRESHOLD, DD_FACTOR = 0.80, 0.5 + # ---- realism model (matches the live copybot) ------------------------------- # Taker fee (Polymarket V2, since 2026-03-30): fee = shares·rate·p·(1−p); for a -# flat-$STAKE buy that's STAKE·rate·(1−p). Sports 0.03 — the follow set's -# category. Redeeming at resolution is fee-free, so only entries pay here +# $stake buy that's stake·rate·(1−p). Sports 0.03 — the follow set's category. +# Redeeming at resolution is fee-free, so only entries pay here # (hold-to-resolution model, no mirrored exits). FEE_RATE = 0.03 # Copy lag: we enter LAG_EST_S after the wallet does, at a slightly worse price. @@ -57,17 +72,26 @@ WALLETS = [ ] -def entry_model(p): - """(effective entry price, entry fee, total cash cost) of a flat-$STAKE copy: +def entry_model(p, stake): + """(effective entry price, entry fee, total cash cost) of a $stake copy: price worsened by the lag-slippage haircut, taker fee on top of the stake.""" p_eff = min(0.999, p * (1 + SLIP)) - fee = STAKE * FEE_RATE * (1 - p_eff) - return p_eff, fee, STAKE + fee + fee = stake * FEE_RATE * (1 - p_eff) + return p_eff, fee, stake + fee _MKT = {} +_SLUG_CACHE = os.path.join(HERE, "slug_cache.json") +try: + _MKT.update(json.load(open(_SLUG_CACHE))) +except Exception: + pass + + def market_meta(cond): - """Market title for display, from the CLOB market endpoint (gamma's condition_ids - filter returns nothing for resolved markets) — cached.""" + """Market title + slug from the CLOB market endpoint (gamma's condition_ids + filter returns nothing for resolved markets) — cached in-process AND on disk + (slug_cache.json), since the event cap needs a slug for every replayed market, + not just the top-60 displayed.""" if cond not in _MKT: try: r = urllib.request.urlopen(urllib.request.Request( @@ -76,10 +100,26 @@ def market_meta(cond): m = json.loads(r.read()) _MKT[cond] = {"title": m.get("question") or "", "slug": m.get("market_slug") or ""} except Exception: - _MKT[cond] = {"title": "", "slug": ""} + return {"title": "", "slug": ""} # transient failure — don't cache return _MKT[cond] +def save_slug_cache(): + try: + json.dump({c: v for c, v in _MKT.items() if v.get("slug")}, + open(_SLUG_CACHE, "w")) + except Exception: + pass + + +def event_key(slug): + """Correlation-group id: Polymarket sub-splits one game across slugs + (`…-2026-07-01-more-markets`, `…-2026-07-01-second-half-result`), so dated + slugs collapse to their `…-YYYY-MM-DD` prefix; undated slugs stand as-is.""" + m = re.match(r"(.*?\d{4}-\d{2}-\d{2})", slug or "") + return m.group(1) if m else (slug or None) + + def conviction_bets(): """Every followed wallet's resolved conviction bets from the cache, with entry time.""" out = [] @@ -145,14 +185,29 @@ def main(): b["kind"] = "open"; by_mkt[b["cond"]] = b stream = sorted(by_mkt.values(), key=lambda b: b["entry_t"]) + # prefetch every replayed market's slug (threaded; disk-cached) so the + # event-correlation cap can group markets by real-world event + with ThreadPoolExecutor(max_workers=8) as ex: + list(ex.map(market_meta, {b["cond"] for b in stream})) + cash = BANK realized = 0.0 fees_paid = 0.0 + hwm = BANK + capped = 0 held = [] # (free_t, cost, payoff) cost = stake + entry fee; payoff paid at free_t perW = {w["wallet"]: {"name": w["name"], "wallet": w["wallet"], "bets": 0, "invested": 0.0, "realized": 0.0} for w in WALLETS} resolved, current, missed = [], [], [] + def cur_stake(): + """PCT of current equity, drawdown-braked, clamped — the copybot's rule.""" + nonlocal hwm + eq = cash + sum(c for _, c, _, _ in held) + hwm = max(hwm, eq) + frac = PCT * (DD_FACTOR if eq < DD_THRESHOLD * hwm else 1.0) + return max(STAKE_MIN, min(STAKE_CAP, frac * eq)) + def free(upto): nonlocal cash, realized keep = [] @@ -167,16 +222,26 @@ def main(): for b in stream: free(b["entry_t"]) - p_eff, fee, cost = entry_model(b["p"]) + stake = cur_stake() + b["stake"] = round(stake, 2) + b["event"] = event_key(market_meta(b["cond"])["slug"]) + # correlation cap: skip a bet when we already hold EVENT_CAP positions on + # the same real-world event (deliberate risk skip, tallied separately) + if b["event"] and sum(1 for _, _, _, r in held + if r.get("event") == b["event"]) >= EVENT_CAP: + b["capped"] = True; capped += 1 + missed.append(b) + continue + p_eff, fee, cost = entry_model(b["p"], stake) if cash >= cost: cash -= cost; fees_paid += fee; perW[b["wallet"]]["bets"] += 1 - shares = STAKE / p_eff # lag-adjusted entry price + shares = stake / p_eff # lag-adjusted entry price if b["kind"] == "res": payoff = shares * (1.0 if b["won"] else 0.0) # redeem is fee-free held.append((b["res_t"] or now, cost, payoff, b)) else: # currently open -> mark to market, no free yet held.append((None, cost, 0.0, b)) - b["val"] = shares * b["cur"]; b["stake"] = STAKE + b["val"] = shares * b["cur"] else: missed.append(b) free(now) @@ -200,10 +265,11 @@ def main(): # indexing m["won"] here used to KeyError and kill the whole portfolio step # the first time capital ran out while a followed wallet had a live position. def hypo_pnl(m): - p_eff, fee, cost = entry_model(m["p"]) + stake = m.get("stake") or STAKE_MIN + p_eff, fee, cost = entry_model(m["p"], stake) if "won" in m: - return (STAKE / p_eff) - cost if m["won"] else -cost - return STAKE * (m.get("cur", p_eff) / p_eff) - cost + return (stake / p_eff) - cost if m["won"] else -cost + return stake * (m.get("cur", p_eff) / p_eff) - cost missed.sort(key=lambda m: m.get("res_t") or 0, reverse=True) for m in missed[:60]: @@ -219,7 +285,9 @@ def main(): equity = cash + invested out = { "started": START, "updated": now, - "bank": BANK, "stake": STAKE, + "bank": BANK, "stake": round(cur_stake(), 2), # the NEXT bet's size + "stake_pct": PCT, "event_cap": EVENT_CAP, "hwm": round(hwm, 2), + "dd_threshold": DD_THRESHOLD, "capped_count": capped, "fee_rate": FEE_RATE, "slip": SLIP, "lag_est_s": LAG_EST_S, "fees_paid": round(fees_paid, 2), "equity": round(equity, 2), "liquid": round(cash, 2), "invested": round(invested, 2), @@ -232,20 +300,23 @@ def main(): "conv_thr": conv_thr.get(v["wallet"], 1e12)} for v in perW.values()], "current": [{"title": c.get("title", ""), "name": c["name"], "outcome": c.get("outcome", ""), - "stake": STAKE, "val": round(c["val"], 2), "pnl": round(c["pnl"], 2), + "stake": c.get("stake"), "val": round(c["val"], 2), "pnl": round(c["pnl"], 2), "end": c.get("end")} for c in sorted(current, key=lambda c: c["entry_t"])], "resolved": [{"title": r.get("title", ""), "name": r["name"], "won": r["won"], - "stake": STAKE, "pnl": round(r["pnl"], 2), "date": r.get("res_t")} + "stake": r.get("stake"), "pnl": round(r["pnl"], 2), "date": r.get("res_t")} for r in resolved[:60]], "missed": [{"title": m.get("title", ""), "name": m["name"], "won": m.get("won"), - "stake": STAKE, "pnl": round(m["pnl"], 2), "date": m.get("res_t")} + "stake": m.get("stake"), "capped": bool(m.get("capped")), + "pnl": round(m["pnl"], 2), "date": m.get("res_t")} for m in missed[:60]], "missed_pnl": round(sum(hypo_pnl(m) for m in missed), 2), } json.dump(out, open(os.path.join(HERE, "portfolio.json"), "w"), separators=(",", ":")) + save_slug_cache() print(f"portfolio: equity ${equity:,.0f} ({(equity-BANK)/BANK*100:+.0f}%) | realized ${realized:+,.0f} " - f"| fees ${fees_paid:,.0f} | {len(resolved)} resolved ({wins}W/{len(resolved)-wins}L) " - f"| {len(current)} open | {len(missed)} missed | -> portfolio.json", flush=True) + f"| fees ${fees_paid:,.0f} | next stake ${cur_stake():,.0f} | {len(resolved)} resolved " + f"({wins}W/{len(resolved)-wins}L) | {len(current)} open | {len(missed)} missed " + f"({capped} event-capped) | -> portfolio.json", flush=True) if __name__ == "__main__":