{"approach": "Bayesian filter on the latent score Z (Gaussian posterior truncated each step to the interval implied by bounds on the true probability, mapped through the jump-diffusion fair-value formula with assumed mid-range jump parameters; reset to uniform on the interval if the posterior is inconsistent, i.e. after a jump). The bounds come from the competitor's best bid/ask (the arbitrageur always sweeps the ladder so bid <= p <= ask) tightened by probing: every step a 0.01-share order is placed on both sides at every tick strictly inside the competitor spread; any probe that was not filled gives a hard bound (an unfilled bid at j means p >= j, an unfilled ask at k means p <= k), narrowing p to about one tick. The predictive distribution of next-step p (5-point quadrature, extra std 0.03 in Z for jump risk) is used to pick, per side, the interior tick maximising RHO*(edge if retail fills) minus Q*(expected arbitrage loss); a 20-share real order is placed there and orders are cancelled and re-placed every step.", "blocked": null, "starter_mean_edge": -6.4826639323115, "score": {"mean_edge": 17.6252574951615, "simulations": 200, "seed_start": 0, "failures": 0}, "holdout": {"mean_edge": 21.328773255399252, "simulations": 400, "seed_start": 10000, "failures": 0}, "code": "from __future__ import annotations\nimport math\nfrom orderbook_pm_challenge.strategy import BaseStrategy\nfrom orderbook_pm_challenge.types import CancelAll, PlaceOrder, Side\n\nSQ2 = math.sqrt(2.0)\nSIG = 0.02\nLAM, MUJ, SJ = 0.0019, 0.0, 0.4\nGH = [(-1.6, 0.11), (-0.8, 0.22), (0.0, 0.34), (0.8, 0.22), (1.6, 0.11)]\n\n\ndef Phi(x):\n    return 0.5 * (1.0 + math.erf(x / SQ2))\n\n\nclass Strategy(BaseStrategy):\n    Q = 20.0\n    RHO = 6.0\n    JUMPSD = 0.03\n    THR = 1e-4\n    MARGIN = 0.0\n\n    def __init__(self):\n        self.m = None\n        self.placed = {}\n        self.v = 0.5\n        self.terms = {}\n\n    def _terms(self, H):\n        t = self.terms.get(H)\n        if t is None:\n            lh = LAM * H\n            w = math.exp(-lh)\n            t = []\n            for n in range(0, 7):\n                if n > 0:\n                    w *= lh / n\n                t.append((w, n * MUJ, math.sqrt(H * SIG * SIG + n * SJ * SJ)))\n            self.terms[H] = t\n        return t\n\n    def f(self, z, H):\n        s = 0.0\n        for w, mu, sd in self._terms(H):\n            s += w * Phi((z + mu) / sd)\n        return s\n\n    def finv(self, p, H):\n        if p <= 0.0:\n            return -9.0\n        if p >= 1.0:\n            return 9.0\n        lo, hi = -9.0, 9.0\n        for _ in range(40):\n            mid = 0.5 * (lo + hi)\n            if self.f(mid, H) < p:\n                lo = mid\n            else:\n                hi = mid\n        return 0.5 * (lo + hi)\n\n    def on_step(self, state):\n        H = max(state.steps_remaining, 1)\n        cb = state.competitor_best_bid_ticks\n        ca = state.competitor_best_ask_ticks\n        plo = (cb / 100.0) if cb else 0.0\n        phi_ = (ca / 100.0) if ca else 1.0\n        if self.placed:\n            rem = {}\n            for o in state.own_orders:\n                rem[(o.side, o.price_ticks)] = o.remaining_quantity\n            for (sd_, tk), q in self.placed.items():\n                nothit = rem.get((sd_, tk), 0.0) >= q - 1e-9\n                if nothit:\n                    if sd_ is Side.BUY:\n                        plo = max(plo, tk / 100.0)\n                    else:\n                        phi_ = min(phi_, tk / 100.0)\n            if plo > phi_:\n                plo = phi_ = 0.5 * (plo + phi_)\n        zlo = self.finv(plo, H)\n        zhi = self.finv(phi_, H)\n        if zhi < zlo:\n            zlo, zhi = zhi, zlo\n        # predict\n        if self.m is None:\n            m, v = 0.5 * (zlo + zhi), 0.0\n            self.m = m\n            self.v = (zhi - zlo) / 3.4641 if zhi > zlo else 0.01\n            m, v = self.m, self.v\n        else:\n            m = self.m\n            v = math.sqrt(self.v ** 2 + SIG ** 2)\n            # truncate to [zlo, zhi]\n            a = (zlo - m) / v\n            b = (zhi - m) / v\n            mass = Phi(b) - Phi(a)\n            if mass < self.THR or v <= 0:\n                m = 0.5 * (zlo + zhi)\n                v = max((zhi - zlo) / 3.4641, 1e-4)\n            else:\n                pa = math.exp(-0.5 * a * a) / 2.5066282746 if abs(a) < 30 else 0.0\n                pb = math.exp(-0.5 * b * b) / 2.5066282746 if abs(b) < 30 else 0.0\n                mu_t = (pa - pb) / mass\n                var = 1.0 + (a * pa - b * pb) / mass - mu_t * mu_t\n                m = m + v * mu_t\n                v = v * math.sqrt(max(var, 1e-6))\n            self.m, self.v = m, v\n        # predictive distribution of p_t (after next move)\n        H2 = max(state.steps_remaining - 1, 1)\n        sd = math.sqrt(self.v ** 2 + SIG ** 2 + self.JUMPSD ** 2)\n        ps = [(self.f(m + g * sd, H2), w) for g, w in GH]\n        pm = sum(p * w for p, w in ps) / sum(w for _, w in ps)\n        acts = [CancelAll()]\n        lo_t = (cb or 0)\n        hi_t = (ca or 100)\n        Q, RHO = self.Q, self.RHO\n        # best bid\n        best, bt = 0.0, None\n        for b in range(max(1, lo_t), min(99, hi_t) + 1):\n            x = b / 100.0\n            if b >= hi_t:\n                continue\n            e = 0.0\n            for p, w in ps:\n                e += w * (RHO * (p - x) - Q * max(0.0, x - p))\n            e /= sum(w for _, w in ps)\n            e -= self.MARGIN\n            if b == lo_t:\n                e *= 0.3\n            if e > best:\n                best, bt = e, b\n        besta, at = 0.0, None\n        for a_ in range(max(1, lo_t), min(99, hi_t) + 1):\n            x = a_ / 100.0\n            if a_ <= lo_t:\n                continue\n            e = 0.0\n            for p, w in ps:\n                e += w * (RHO * (x - p) - Q * max(0.0, p - x))\n            e /= sum(w for _, w in ps)\n            e -= self.MARGIN\n            if a_ == hi_t:\n                e *= 0.3\n            if e > besta:\n                besta, at = e, a_\n        placed = {}\n        if state.free_cash > 4 * Q + 20:\n            for tk in range(lo_t + 1, hi_t):\n                if tk < 1 or tk > 99:\n                    continue\n                qb = Q if tk == bt else 0.01\n                qa = Q if tk == at else 0.01\n                acts.append(PlaceOrder(Side.BUY, tk, qb)); placed[(Side.BUY, tk)] = qb\n                acts.append(PlaceOrder(Side.SELL, tk, qa)); placed[(Side.SELL, tk)] = qa\n        self.placed = placed\n        return acts\n", "code_sha256": "8ff9478cbd1f986b1030c5b053e9ce1b8ac3c4310122e6703167a51250b4b8ba", "tried": ["Quote 1 tick inside competitor bid/ask, size 20, no estimate: -129.9 (28 of 200 sims failed on collateral)", "Filter on competitor bracket only, choose tick by expected edge, size 6/rho 3: about -18.6 (40 sims); smaller sizes -3 to -10, still negative: bracket is too wide, arbitrageur takes stale quotes", "Add tiny probe orders at every interior tick so unfilled probes bound p (plus Q=6,RHO=3): about +12 to +27 depending on seeds (60 sims 12.0)", "Tune size/rho on 60 sims: Q=20,RHO=6 about 19.6; Q=50 about 15.5; Q=6 about 12", "Second layer of real orders on other interior ticks (Q2=10 or 20): 13.2 and 12.1 vs 19.5 without; rejected", "Extra jump-risk std in Z of 0.02 to 0.05: 16.4 to 19.5, kept 0.03 (80 sims 19.5)", "Checks: local starter score is -6.4827 not the server -6.504278 (mismatch, so local scores may differ slightly from the leaderboard). --sandbox could not be verified here: on this machine (Python 3.12) the sandbox loader itself fails with ImportError for pathlib/ntpath for every strategy including the unmodified starter, so all 20 runs failed regardless of code. The strategy imports only math and orderbook_pm_challenge, both on the allowlist.", "Final: Q=20, RHO=6, JUMPSD=0.03 with probes, scores in score/holdout fields. By competitor spread (80 sims): spread 1 about 1, 2 about 6, 3 about 22, 4 about 54 edge, so the score is driven by regimes with a wide competitor spread"], "next": "The edge is capped by the interior ticks between the competitor quotes (spread 1-2 regimes give almost nothing). Next: learn the jump and retail parameters online from fills, model retail size to size orders per tick, test quoting at the competitor's best level to catch large retail orders, filter with a full grid posterior instead of a truncated Gaussian, and use own partial-fill amounts to separate retail from arbitrageur fills."}