{
 "approach": "Blind build from the simulator only. S0 (<150 lines): estimates last step's probability as an interval [lo,hi] in ticks. Unswept hidden-competitor best bid/ask bound it, and so do our own last quotes (an unfilled bid means p>=bid; a fully filled bid with no ask fill means p<=bid). It quotes one tick inside the competitor, at least 0.5 tick from the estimate, with a fixed lot. A side is quoted only if FILL_PROB*depth beats the expected arbitrage loss sigma*L(depth/sigma), where sigma is the model's per-step probability move at p and steps remaining. S1 = S0 plus a finite-horizon DP over net inventory (grid of +-60 lots, reflecting) and steps remaining (solved once, 2000 steps). V_h(q) = V_{h-1}(q) - GAMMA*var_h*q^2 + f*max(0, e + V_{h-1}(q+1) - V_{h-1}(q)) + f*max(0, e + V_{h-1}(q-1) - V_{h-1}(q)). The bracketed gains are the value of one more / one fewer YES lot. Each side's lot = LOT*clamp(gain/e, 0, 2), so S1 sizes up the side that reduces inventory and shrinks or drops the side that adds to it. With GAMMA=0 it reproduces S0 exactly (tuning diff 0.003). The edge score has no terminal inventory term and cash capacity never bound (S0 max |inventory| median ~210 shares). So the gain comes from inventory acting as an adverse-selection signal: it cuts arbitrage loss (+2.18) and costs some retail (-1.04).",
 "tried": [
  {
   "variant": "S0 LOT=15 FILL_PROB=0.05",
   "seeds": "115000..115499",
   "n": 500,
   "mean": 4.91,
   "se": 0.875
  },
  {
   "variant": "S0 LOT=25 FILL_PROB=0.05",
   "seeds": "115000..115499",
   "n": 500,
   "mean": 2.129,
   "se": 1.327
  },
  {
   "variant": "S0 LOT=15 FILL_PROB=0.03 (frozen S0)",
   "seeds": "115000..115499",
   "n": 500,
   "mean": 5.917,
   "se": 0.821
  },
  {
   "variant": "S0 LOT=25 FILL_PROB=0.03",
   "seeds": "115000..115499",
   "n": 500,
   "mean": 4.474,
   "se": 1.243
  },
  {
   "variant": "S1 GAMMA=0 (capacity only)",
   "seeds": "115000..115499",
   "n": 500,
   "mean": 5.92,
   "paired_diff_vs_S0": 0.003,
   "se": 0.0
  },
  {
   "variant": "S1 GAMMA=2e-4",
   "seeds": "115000..115499",
   "n": 500,
   "mean": 7.002,
   "paired_diff_vs_S0": 1.085,
   "se": 0.103
  },
  {
   "variant": "S1 GAMMA=1e-3 (frozen S1)",
   "seeds": "115000..115499",
   "n": 500,
   "mean": 7.403,
   "paired_diff_vs_S0": 1.485,
   "se": 0.127
  },
  {
   "variant": "S1 GAMMA=5e-3",
   "seeds": "115000..115499",
   "n": 500,
   "mean": 7.112,
   "paired_diff_vs_S0": 1.195,
   "se": 0.202
  },
  {
   "variant": "pre-tuning smoke/debug runs, not used for selection beyond picking the variant grid: an S0 mid-quote prototype and four LOT/FILL_PROB probes, plus one S1 run",
   "seeds": "115000..115021",
   "n": 22,
   "note": "inside the tuning range"
  }
 ],
 "next": "The mechanism gives a real but small gain (+1.1 on a weak S0 at ~7, far below B at ~100). It works through inventory as a toxicity signal, not through risk or capacity, because the score ignores inventory. Graft it onto B with GAMMA rescaled to B's lot size and edge per share, and measure paired against B. A cheaper alternative to test: a direct lot skew linear in inventory, k*q, which the DP approximates. Learning DP_EDGE and DP_FILL online per simulation is untested.",
 "code": "import math\nfrom orderbook_pm_challenge.strategy import BaseStrategy\nfrom orderbook_pm_challenge.types import CancelAll, PlaceOrder, Side\n\nSIGMA = 0.02          # diffusion sigma per step (simulator default)\nJUMP_VAR = 0.0019 * 0.16  # mid-band jump intensity * jump_sigma^2 per step\n\n\ndef _pdf(x):\n    return math.exp(-0.5 * x * x) / math.sqrt(2.0 * math.pi)\n\n\ndef _cdf(x):\n    return 0.5 * (1.0 + math.erf(x / math.sqrt(2.0)))\n\n\ndef _inv_cdf(p):\n    p = min(max(p, 1e-6), 1 - 1e-6)\n    lo, hi = -8.0, 8.0\n    for _ in range(50):\n        mid = 0.5 * (lo + hi)\n        if _cdf(mid) < p:\n            lo = mid\n        else:\n            hi = mid\n    return 0.5 * (lo + hi)\n\n\nclass Strategy(BaseStrategy):\n    \"\"\"S1 = S0 plus an inventory dynamic programme. S0: one tick inside the hidden competitor, fixed lot, sides gated by\n    expected retail edge versus expected arbitrage loss.\"\"\"\n\n    LOT = 15.0          # shares per quote\n    FILL_PROB = 0.03   # assumed chance per step that a retail order reaches our quote\n    MIN_EDGE = 0.5     # min distance in ticks between quote and estimated probability\n\n    def __init__(self):\n        self.est = None\n        self.quotes = (None, 0.0, None, 0.0)  # bid tick, bid qty, ask tick, ask qty\n\n    def estimate(self, state):\n        # interval for last step's probability: unswept competitor and own quotes bound it\n        b, a = state.competitor_best_bid_ticks, state.competitor_best_ask_ticks\n        lo = float(b) if b is not None else 0.5\n        hi = float(a) if a is not None else 99.5\n        qb, nb, qa, na = self.quotes\n        if qb is not None:\n            if state.buy_filled_quantity <= 0.0:\n                lo = max(lo, qb)\n            elif state.buy_filled_quantity >= nb - 1e-9 and state.sell_filled_quantity <= 0.0:\n                hi = min(hi, qb)\n        if qa is not None:\n            if state.sell_filled_quantity <= 0.0:\n                hi = min(hi, qa)\n            elif state.sell_filled_quantity >= na - 1e-9 and state.buy_filled_quantity <= 0.0:\n                lo = max(lo, qa)\n        if lo > hi:\n            lo, hi = hi, lo\n        est = 0.5 * (lo + hi) if self.est is None else min(max(self.est, lo), hi)\n        if self.est is not None and hi - lo <= 2.0:\n            est = 0.5 * (est + 0.5 * (lo + hi))\n        self.est = min(max(est, 1.0), 99.0)\n        return self.est\n\n    def step_sigma_ticks(self, p_ticks, h):\n        z = _inv_cdf(p_ticks / 100.0)\n        per = SIGMA * SIGMA + JUMP_VAR\n        return 100.0 * _pdf(z) * math.sqrt(per / max(h * per, per))\n\n    def side_ok(self, depth, sig):\n        if depth < self.MIN_EDGE:\n            return False\n        x = depth / max(sig, 1e-9)\n        arb_loss = sig * (_pdf(x) - x * (1.0 - _cdf(x)))\n        return self.FILL_PROB * depth > arb_loss\n\n    # --- inventory DP: value of one more YES share, used to size and skew quotes ---\n    GAMMA = 1e-3       # per-step penalty on variance of inventory mark-to-market\n    DP_FILL = 0.05     # chance per step that a quoted lot is filled\n    DP_EDGE = 0.0021   # expected edge per filled share, dollars\n    DP_K = 60          # inventory grid half-width in lots (reflecting capacity)\n\n    def solve_dp(self, n):\n        K, L, f = self.DP_K, self.LOT, self.DP_FILL\n        e = self.DP_EDGE * L\n        v = [0.0] * (2 * K + 1)\n        self.mv = [None] * (n + 1)  # mv[h] = (gain of a bid lot, gain of an ask lot) per inventory\n        for h in range(1, n + 1):\n            var = (self.step_sigma_ticks(50.0, h) / 100.0) ** 2\n            gb = [e + v[i + 1] - v[i] if i < 2 * K else -1.0 for i in range(2 * K + 1)]\n            ga = [e + v[i - 1] - v[i] if i > 0 else -1.0 for i in range(2 * K + 1)]\n            self.mv[h] = (gb, ga)\n            v = [v[i] - self.GAMMA * var * ((i - K) * L) ** 2 + f * max(gb[i], 0.0) + f * max(ga[i], 0.0)\n                 for i in range(2 * K + 1)]\n\n    def lots(self, state, p, sig):\n        if getattr(self, \"mv\", None) is None:\n            self.solve_dp(state.steps_remaining)\n        h = min(max(state.steps_remaining, 1), len(self.mv) - 1)\n        gb, ga = self.mv[h]\n        x = (state.yes_inventory - state.no_inventory) / self.LOT + self.DP_K\n        x = min(max(x, 0.0), 2.0 * self.DP_K)\n        i = min(int(x), 2 * self.DP_K - 1)\n        w = x - i\n        e = self.DP_EDGE * self.LOT\n        b = (1 - w) * gb[i] + w * gb[i + 1]\n        a = (1 - w) * ga[i] + w * ga[i + 1]\n        return self.LOT * min(max(b / e, 0.0), 2.0), self.LOT * min(max(a / e, 0.0), 2.0)\n\n    def on_step(self, state):\n        actions = [CancelAll()]\n        p = self.estimate(state)\n        sig = self.step_sigma_ticks(p, state.steps_remaining)\n        bid_lot, ask_lot = self.lots(state, p, sig)\n        cash = state.cash\n        b, a = state.competitor_best_bid_ticks, state.competitor_best_ask_ticks\n        bid = (b + 1) if b is not None else int(math.floor(p - 2))\n        ask = (a - 1) if a is not None else int(math.ceil(p + 2))\n        bid = min(bid, int(math.floor(p - self.MIN_EDGE)))\n        ask = max(ask, int(math.ceil(p + self.MIN_EDGE)))\n        self.quotes = (None, 0.0, None, 0.0)\n        if 1 <= bid <= 99 and bid_lot >= 0.01 and self.side_ok(p - bid, sig):\n            q = math.floor(min(bid_lot, cash / (bid / 100.0)) * 100) / 100\n            if q >= 0.01:\n                actions.append(PlaceOrder(side=Side.BUY, price_ticks=bid, quantity=q))\n                self.quotes = (bid, q, None, 0.0)\n                cash -= q * bid / 100.0\n        if 1 <= ask <= 99 and ask_lot >= 0.01 and self.side_ok(ask - p, sig):\n            cover = min(ask_lot, state.yes_inventory)\n            unc_cost = (1 - ask / 100.0)\n            q = cover + max(0.0, min(ask_lot - cover, cash / unc_cost))\n            q = math.floor(q * 100) / 100\n            if q >= 0.01:\n                actions.append(PlaceOrder(side=Side.SELL, price_ticks=ask, quantity=q))\n                self.quotes = (self.quotes[0], self.quotes[1], ask, q)\n        return actions\n",
 "code_sha256": "babe7b41cd1b556bbd22ca4ffa44777a3c5d570e7d8a761b90730b6233ea4375",
 "code_s0": "import math\nfrom orderbook_pm_challenge.strategy import BaseStrategy\nfrom orderbook_pm_challenge.types import CancelAll, PlaceOrder, Side\n\nSIGMA = 0.02          # diffusion sigma per step (simulator default)\nJUMP_VAR = 0.0019 * 0.16  # mid-band jump intensity * jump_sigma^2 per step\n\n\ndef _pdf(x):\n    return math.exp(-0.5 * x * x) / math.sqrt(2.0 * math.pi)\n\n\ndef _cdf(x):\n    return 0.5 * (1.0 + math.erf(x / math.sqrt(2.0)))\n\n\ndef _inv_cdf(p):\n    p = min(max(p, 1e-6), 1 - 1e-6)\n    lo, hi = -8.0, 8.0\n    for _ in range(50):\n        mid = 0.5 * (lo + hi)\n        if _cdf(mid) < p:\n            lo = mid\n        else:\n            hi = mid\n    return 0.5 * (lo + hi)\n\n\nclass Strategy(BaseStrategy):\n    \"\"\"S0: one tick inside the hidden competitor, fixed lot, sides gated by\n    expected retail edge versus expected arbitrage loss.\"\"\"\n\n    LOT = 15.0          # shares per quote\n    FILL_PROB = 0.03   # assumed chance per step that a retail order reaches our quote\n    MIN_EDGE = 0.5     # min distance in ticks between quote and estimated probability\n\n    def __init__(self):\n        self.est = None\n        self.quotes = (None, 0.0, None, 0.0)  # bid tick, bid qty, ask tick, ask qty\n\n    def estimate(self, state):\n        # interval for last step's probability: unswept competitor and own quotes bound it\n        b, a = state.competitor_best_bid_ticks, state.competitor_best_ask_ticks\n        lo = float(b) if b is not None else 0.5\n        hi = float(a) if a is not None else 99.5\n        qb, nb, qa, na = self.quotes\n        if qb is not None:\n            if state.buy_filled_quantity <= 0.0:\n                lo = max(lo, qb)\n            elif state.buy_filled_quantity >= nb - 1e-9 and state.sell_filled_quantity <= 0.0:\n                hi = min(hi, qb)\n        if qa is not None:\n            if state.sell_filled_quantity <= 0.0:\n                hi = min(hi, qa)\n            elif state.sell_filled_quantity >= na - 1e-9 and state.buy_filled_quantity <= 0.0:\n                lo = max(lo, qa)\n        if lo > hi:\n            lo, hi = hi, lo\n        est = 0.5 * (lo + hi) if self.est is None else min(max(self.est, lo), hi)\n        if self.est is not None and hi - lo <= 2.0:\n            est = 0.5 * (est + 0.5 * (lo + hi))\n        self.est = min(max(est, 1.0), 99.0)\n        return self.est\n\n    def step_sigma_ticks(self, p_ticks, h):\n        z = _inv_cdf(p_ticks / 100.0)\n        per = SIGMA * SIGMA + JUMP_VAR\n        return 100.0 * _pdf(z) * math.sqrt(per / max(h * per, per))\n\n    def side_ok(self, depth, sig):\n        if depth < self.MIN_EDGE:\n            return False\n        x = depth / max(sig, 1e-9)\n        arb_loss = sig * (_pdf(x) - x * (1.0 - _cdf(x)))\n        return self.FILL_PROB * depth > arb_loss\n\n    def lots(self, state, p, sig):\n        return self.LOT, self.LOT\n\n    def on_step(self, state):\n        actions = [CancelAll()]\n        p = self.estimate(state)\n        sig = self.step_sigma_ticks(p, state.steps_remaining)\n        bid_lot, ask_lot = self.lots(state, p, sig)\n        cash = state.cash\n        b, a = state.competitor_best_bid_ticks, state.competitor_best_ask_ticks\n        bid = (b + 1) if b is not None else int(math.floor(p - 2))\n        ask = (a - 1) if a is not None else int(math.ceil(p + 2))\n        bid = min(bid, int(math.floor(p - self.MIN_EDGE)))\n        ask = max(ask, int(math.ceil(p + self.MIN_EDGE)))\n        self.quotes = (None, 0.0, None, 0.0)\n        if 1 <= bid <= 99 and bid_lot >= 0.01 and self.side_ok(p - bid, sig):\n            q = math.floor(min(bid_lot, cash / (bid / 100.0)) * 100) / 100\n            if q >= 0.01:\n                actions.append(PlaceOrder(side=Side.BUY, price_ticks=bid, quantity=q))\n                self.quotes = (bid, q, None, 0.0)\n                cash -= q * bid / 100.0\n        if 1 <= ask <= 99 and ask_lot >= 0.01 and self.side_ok(ask - p, sig):\n            cover = min(ask_lot, state.yes_inventory)\n            unc_cost = (1 - ask / 100.0)\n            q = cover + max(0.0, min(ask_lot - cover, cash / unc_cost))\n            q = math.floor(q * 100) / 100\n            if q >= 0.01:\n                actions.append(PlaceOrder(side=Side.SELL, price_ticks=ask, quantity=q))\n                self.quotes = (self.quotes[0], self.quotes[1], ask, q)\n        return actions\n",
 "tuning_score": {
  "seeds": "115000..115499",
  "n": 500,
  "s1_mean": 7.403,
  "s0_mean": 5.917
 },
 "paired": {
  "seeds": "200000..200999",
  "n": 1000,
  "complete": true,
  "mechanism_diff": 1.1374,
  "mechanism_se": 0.0898,
  "mechanism_retail_diff": -1.0419,
  "mechanism_arb_diff": 2.1793,
  "reference_diff": -91.6037,
  "reference_se": 5.3678,
  "s1_mean": 8.0922,
  "s0_mean": 6.9547,
  "b_mean": 99.6959,
  "failures": [],
  "notes": "retail diff se 0.049, arb diff se 0.073; b_mean is the fixture's mean over seeds 200000..200999 (sha256 verified)"
 },
 "graft": "Solve once at step 0: V_h(q)=V_{h-1}(q)-G*var_h*q^2+f*max(0,e+V_{h-1}(q+1)-V_{h-1}(q))+f*max(0,e+V_{h-1}(q-1)-V_{h-1}(q)), grid of +-60 lots, var_h = model per-step probability variance. Each step, interpolate the two gains at the net YES-NO inventory and scale the bid/ask lots by clamp(gain/e,0,2). Rescale G (1e-3 here), e (edge per lot) and f (fill rate per lot per step) to the host strategy.",
 "seconds_per_simulation": {
  "s0": 0.103,
  "s1": 0.189,
  "note": "wall time per simulation, 22 single-process simulations running concurrently on a 24-core machine; S1 includes ~0.09 s for the one-off DP solve"
 }
}