# oracle-price: ESTIMATION ORACLE bound for NP2 **Headline:** if NP2 is given the true fair price p_t in place of its estimate pHat, with all of its other logic kept, it does **not** improve. The best version scores 2.11 ± 0.04 edge per sim **below** NP2 on the confirm block. So on this fee formula, a better fair-price estimate alone buys nothing. It makes things slightly worse, because NP2's constants are tuned to how its own pHat behaves. ## method - **Simulator:** github.com/benedictbrady/amm-challenge @ 6b6921f8d463314aa7c96234e1be25a25245f704. It was cloned into the workspace and built with the goal's setup.sh (uv venv on Python 3.12, `maturin develop --release`). - **NP2:** TWO's `code` with NP's block (from `// Interior retail routing` up to but not including `uint256 tradeRatio =`) inserted before TWO's `uint256 tradeRatio =`. sha256 51f1c1a9d4999ee460eb8bbc66880da8074b173399a4f5d71f96671482e18ac0. On seeds 0..999 with the unmodified build it scored **526.115572246** (target 526.1156 ✓). - **What the oracle knows:** the true GBM fair price p_t of the current step. This is `fair_price = price_process.step()` in `engine.rs`, set on both AMMs before that step's arb and retail trades. It is the same price that the arbitrageur and the edge accounting use. - **How it is hooked in: p_t is passed in as an extra argument; NP2's fee formula is not ported.** - `EVMStrategy::after_swap(trade, oracle_price)` builds a 228-byte calldata: the unchanged 196-byte ABI `afterSwap(TradeInfo)` call, then one extra 32-byte word holding p_t as a WAD (Y per X, the same units as NP2's `spot = reserveY/reserveX`). - `CFMM` has a new `oracle_price` field, which the engine sets each step and `update_fees` forwards. - Solidity's ABI decoder ignores trailing calldata, so unmodified strategies behave the same. - The oracle contract reads the extra word with `uint256(bytes32(msg.data[196:228]))`. - The engine also accumulates per-strategy arb edge (−arb profit), retail edge at fair price, and the per-step |bid−ask|. These are returned in a new `extra` result field. - Every draw, the arb rule, the router/retail split and the 30 bps normalizer are untouched. - p_t is only available inside `afterSwap`. `afterInitialize` is unchanged, which matches NP2: its initial fees are BASE_FEE and do not depend on pHat. - **Oracle strategy:** NP2 source byte-for-byte except for four edits: 1. `tox = |spot − p_t|/p_t × TOX_SCALE`, replacing `|spot − pHat|/pHat`. This is the gap term and feeds toxEma → TOX/TOX_QUAD/SIGMA_TOX/DIR_TOX/STALE_DIR. 2. The stale-shift side test is `spot >= p_t`. 3. The trade-aligned boost test is `spot >= p_t` / `spot < p_t`. 4. The `_pTrue()` reader. Everything else is unchanged: pHat's own update (gate, alphas, interior-routing nudge), sigmaHat, lambda, size, dir, act, skews, the tail compress and all constants. At TOX_SCALE = 1 this is exactly "NP2 with pHat → p_t". The chosen oracle (TOX_SCALE = 0.9) has sha256 a9e1c1b918656678d244d5cd4ab0d01869c69bd66e51b6c4af6f6d1b0b4c0e44. - **Pairing:** `amm_sim_rs.run_batch(strategy, vanilla30bps, configs)`, with each config drawn the way `MatchRunner._build_configs` draws it: `default_rng(seed)` → uniform size, rate, sigma in that order; the price process uses `seed` and retail uses `seed+1`. Both sides get the same seed list. The score is the submission's edge. - **Baseline proof:** NP2 through the modified path (228-byte calldata, new build) vs NP2 on the original build, on seeds 0..19. The per-seed edge differences were **exactly 0.0 for all 20 seeds** (max |diff| = 0.0). ## paired Oracle (TOX_SCALE = 0.9) minus NP2, on confirm seeds **2230000..2232499**. This is a contiguous, fresh block, used once. ```json {"n": 2500, "mean_diff": -2.1087, "se": 0.0385, "worst": -14.6943} ``` - se = sd/sqrt(n). The best seed was +6.3179, and the oracle won on 11.9% of seeds. - **Missing:** confirm seeds 2232500..2239999 were not run. At about 155 s per 1000 seeds per strategy, the full 10,000-seed block (~52 min for both sides) did not fit the time left. The block was run in order in 500-seed chunks; the running mean was steady at −2.19, −2.15, −2.08, −2.10, −2.11. ## decomp Mean per sim on the confirm block (n = 2500), edge at the true fair price. Arb edge = −(arb profit); retail edge = Σ retail trade edge at p_t. | | arb edge | retail edge | total edge | |---|---|---|---| | oracle (0.9) | −21.6908 | 543.7294 | 522.0385 | | NP2 | −31.0855 | 555.2328 | 524.1473 | | oracle − NP2 | **+9.3947** | **−11.5034** | −2.1087 | The oracle **loses 9.39 less to arbs** but **takes 11.50 less retail edge**. Knowing p_t lets the true gap mark the stale side reliably, but the same terms then over-protect: they raise the fee on the side retail would use and give away more retail than the arb saving. The bound does not come from "taking more retail". ## fee_stats Confirm block, mean over sims of the per-step (end-of-step) fee quote, in bps. | | mean bid fee | mean ask fee | mean \|bid−ask\| | |---|---|---|---| | oracle (0.9) | 39.137 | 38.283 | 67.106 | | NP2 | 36.258 | 35.429 | 54.586 | The oracle quotes about 2.9 bps higher fees on both sides and is much more asymmetric (+12.5 bps |bid−ask|). The bigger, more reliable gap signal pushes the stale/attract shift harder. ## tuning - One knob: **TOX_SCALE** multiplies the true-gap tox. At 1.0 the oracle is literally NP2 with pHat → p_t. - Tuned on seeds 10000..10299 (300 seeds, a subset of the allowed 10000..10999, to fit the time), paired vs NP2 on the same seeds. | TOX_SCALE | mean_diff | se | |---|---|---| | 0 | −145.3593 | 2.0370 | | 0.25 | −79.0782 | 1.1034 | | 0.5 | −28.5886 | 0.4525 | | 0.75 | −3.5863 | 0.1494 | | **0.9 (chosen)** | **−2.3079** | **0.1093** | | 1.0 | −6.2938 | 0.1488 | | 1.2 | −26.7940 | 0.3984 | - An early 20-seed check of 1.0 on seeds 0..19 gave −5.26. - Not tried: values between 0.75 and 0.9; tuning other constants (sigma, skew or shift coefficients) jointly with p_t; using the drawn sigma in place of sigmaHat. The brief's variant keeps NP2's sigmaHat. - Every value tried was below NP2, so the bound for this mechanism is ≤ 0 within the grid. ## lesson Replacing pHat with the true price did not help; it lost 2.1. NP2's gap terms are tuned to a pHat that is re-anchored to fee-adjusted trade prints, so after an arb the gap is small. With the true price the post-arb gap is about one full fee width, which over-shifts fees and costs retail (−11.5) more than it saves on arbs (+9.4). A Solidity strategy that sees only its own swaps should not chase a better price estimate. Gains have to come from new mechanisms that trade arb protection against retail capture, such as size- or side-aware shifts.