Skip to content

Noise modelling fixes and data updates - #1

Merged
tiago-hansen merged 134 commits into
mainfrom
noise-modelling-fixes-data
Jul 9, 2026
Merged

tiago-hansen merged 134 commits into
mainfrom
noise-modelling-fixes-data

Conversation

@mendesfabio

Copy link
Copy Markdown
Member

Brings main up to date with the latest noise-modelling work, since external testers were told to run from this branch.

Includes (see commit history for detail):

  • Noise-model fixes and calibration updates (market_linear parametrisation, thermostat comparisons)
  • Quote-currency candidates + Coinbase live fallback in update_historic_data
  • MM artifact, competitor TVL data, and winning sweep results
  • reCLAMM demo/compare script improvements, theming, CSV exports and plots

Known issue on this branch (fixed separately in a stacked PR): scripts/reclamm/demo_run_reclamm.py AAVE/ETH scenarios crash with KeyError: 'price_ratio'_apply_active_noise_settings builds a cfg without price_ratio/centeredness_margin/daily_price_shift_exponent, which normalize_compare_run_cfg (added in 8331b7f) hard-indexes.

🤖 Generated with Claude Code

bulkcade and others added 30 commits March 3, 2026 17:49
…ner flexibility and less piping changes for pools requiring reserve inputs
…rades is the wrong shape and optional. Centralised in the new materialized function that is used by all dynamic input reserve calcs
…atio changes etc for gradient based optimisation methods
The merge of dev into reclamm-phase-1 reintroduced a reference to the
old fees_array/gas_cost_array/arb_fees_array/trades_array parameters in
the fused reserves guard. These were replaced by the DynamicInputArrays
container in the dynamic inputs refactor. Replace the stale check with
`dynamic_inputs is None`, which is the correct guard under the new API.
…pdates

Replace the ad-hoc "keep overvalued, solve undervalued" virtual balance
recalculation with the closed-form quadratic from ReClammMath.sol
computeVirtualBalancesUpdatingPriceRatio. The old code silently drove
centeredness to 1.0 for off-center pools.

Add parametrized test mirroring the Foundry fuzz test
testCalculateVirtualBalancesUpdatingPriceRatio__Fuzz, asserting that
centeredness is preserved and the target price ratio is achieved.
Tests that construct run_fingerprint dicts directly (bypassing
recursive_default_set) need the ste_temperature key now that the
STE-enabled scan steps read it from the fingerprint.
The fees_gate STE was unnecessary — gamma (1 - fees) is either a static
config value or a learnable param constrained to be nonzero, so the
fee/zero-fee branch selection never benefits from soft gradients.
…ly forwarding

- base_pool: add calculate_volatility_array and _handle_numeraire_ordering
- optimal_n_pool_arb: use relative invariant check instead of absolute slack
- balancer/balancer_reserves: forward lp_supply through scan functions
- balancer: use materialized_inputs.lp_supply in dynamic_inputs path
- TFMM_base_pool: use materialized_inputs.lp_supply, fix trade_array ref
- core_simulator/__init__: enable JAX compilation cache
Three noise trade volume models for reCLAMM pools, each predicting
non-arbitrage trading volume from pool TVL, fee tier, volatility,
and arb volume. Used by the noise model dispatch in scan steps.
… steps

Scan step carry now includes prev_lp_supply (pos 3) and price-ratio
schedule state (pos 4-9). Scan inputs include price_ratio_update (7),
lp_supply (8), and optional volatility (9).

- reclamm_reserves: LP supply scaling, noise model dispatch (ratio,
  tsoukalas_sqrt, tsoukalas_log, loglinear, arb_only) in scan steps
- reclamm: _prepare_dynamic_array for volatility, noise/LP kwargs
  threading through all pool method variants
- tests: LP supply and noise volume coverage
Hierarchical Bayesian noise model calibration using numpyro: data
pipeline, covariate encoding, model definitions, inference, BLUP
postprocessing, diagnostic plotting, and CLI.

Adds [calibration] optional deps (numpyro, arviz) to pyproject.toml
and updates CI to install them.
MatthewWilletts and others added 29 commits May 11, 2026 16:06
…lot script

run_period_sweep.sh:
- New OVERFITTING_PENALTIES axis (defaults: ["" "1.0" "5.0"]); each
  value is passed as --overfitting-penalty to the tune script.
- New ROBUST_TEMPS axis (defaults: ["" "0.5" "1.0" "0.1" "0.01"]); each
  value is passed as --robust-temperature.
- New long_2021 period (2021-06-01 → 2025-01-01 train+val, → 2026-03-01
  test) with optional --val-fraction parameter to give it a 40% val
  holdout matching its longer history.
- Tags now combine objective + robust + penalty + period; existing
  result files are skipped on rerun.
- Single-array overrides retained at the bottom of each axis block as
  runtime knobs.

plot_sweep_results.py:
- Refactor from single-pool BASE_FP/PERIODS into POOL_CONFIGS dict
  keyed by pool (aave_eth, cow_eth_mainnet, cow_eth_base) so the same
  script can plot sweep results for multiple pools.
- Filenames now have suffix _mainnet/_base where needed.
- Filename parsing in load_sweep_results recognises robust and penalty
  suffixes via _parse_variant; ALLOWED_ROBUST and ALLOWED_PENALTY
  constants control which combinations are loaded (defaults match the
  current run_period_sweep.sh active axes).
- _short_label translates _robust0.5/_penalty5.0 to " r0.5"/" p5.0"
  for compact legend entries.
- New --top N CLI flag ranks configs by test-period RoH and plots only
  the top N (plus on-chain baselines), saved with _topN suffix.
- long_2021 added to aave_eth periods list.
Four scripts that together define the soup-to-nuts pipeline for a full
reClAMM parameter sweep + selection + forward-simulation cycle.

scripts/run_full_sweep.sh
  Bash entry point for the parameter sweep. Iterates over a CONFIGS list
  (pool × TVL × gas × fees), objectives, and overfitting penalties, with
  optional --method=optuna|cma_es and --pair=<prefix> filters. Throttles
  to MAX_PARALLEL workers; CMA-ES defaults to 4, Optuna to 8 (or the
  MAX_WORKERS env override). PR_MAX env var caps the price-ratio search
  via --pr-max for Optuna runs. Existing result files are skipped so
  reruns are incremental.

scripts/evaluate_trials.py
  General-purpose loader and analyser for the result hash files written
  to results/. Walks the trial JSONs, deserialises params, and exposes
  load_reclamm_results which returns a list of dicts with study_id,
  tokens, initial_pool_value, params, train/val metrics, return_val
  (objective), and timestamps. Used by select_best_params and
  run_final_sims to discover candidate trials without re-running.

scripts/select_best_params.py
  Picks the winning param set per (tokens, TVL). Ranks candidates by
  validation RoH (best_val_roh) and prints the chosen trial along with
  PR/margin/shift values, train/val metrics, and the source study_id.
  --all summarises every TVL config in one go.

scripts/run_final_sims.py
  Forward-simulation runner. For each pair × TVL, loads selected params
  via select_best_params, builds noise arrays for both train and test
  periods (calling build_mm_simulator_arrays when not cached), runs
  do_run_on_historic_data, and dispatches to plot_reclamm_optuna_result
  for value / fee_revenue / volume / weight panels. Normalises all
  output by initial TVL so different TVL levels are directly
  comparable on a single plot, and records the initial_tvl per config
  so the volume panel can express cumulative volume as a multiple of
  TVL.
… keys

_prepare_noise_arrays now returns a dict instead of a (vol, dow_sin,
dow_cos) tuple; ReClammPool unpacks it via .get(...) to forward whatever
keys the chosen noise model needs:

- "ratio" / legacy paths: volatility, dow_sin, dow_cos
- "market_linear": adds noise_base, noise_tvl_coeff
- "mm_observed":   adds noise_base, competitor_tvl

This matches the dict-based API used downstream by
_jax_calc_reclamm_reserves_with_dynamic_inputs and avoids forcing the
caller to know which scalar arrays the active noise model expects.
prepare_dynamic_inputs builds lp_supply_array at minute resolution from
the LP supply timeseries, but the pool scan loop iterates at
arb_frequency-minute steps. materialize_dynamic_inputs then expects
arrays whose length matches the scan_len, so a minute-resolution
lp_supply_array silently mismatches for arb_frequency > 1.

Fix by stride-subsampling lp_supply_array (and its test-period
counterpart) by arb_frequency before they leave prepare_dynamic_inputs.
No effect when arb_frequency == 1.
Previously only lp_supply_array was subsampled. fees_array, gas_cost_array,
and arb_fees_array share the same shape contract — materialize_dynamic_inputs
requires scan_len = (bout_length - 1) // arb_frequency, so any non-None
minute-resolution array would have raised in _broadcast_dynamic_input_leaf
once a caller populated those DataFrames with arb_frequency > 1. The bug
never tripped because current callers use scalar fees/gas/arb_fees. Apply
the same [::arb_freq] slice to all four (train + test) so the path is
consistent and future callers can pass per-minute series safely.
Extend the noise-model dispatch already used in reCLAMM to BalancerPool's
two reserve solvers (with_fees_using_precalcs and with_dynamic_inputs).
Supports ratio, tsoukalas_sqrt / tsoukalas_log / loglinear, calibrated,
market_linear, and mm_observed — the same set the reCLAMM path handles.

Noise arrays are appended to the scan inputs at the same positions as in
reclamm_reserves.py so the dispatch logic mirrors line-for-line, and
noise volume is converted to noise-fee income (minus protocol_fee_split)
and rebated to LPs via a uniform reserves scale, matching reCLAMM's
treatment. _prepare_noise_arrays lives on BalancerPool but is a verbatim
port of the reCLAMM helper — the noise model describes market-level
organic volume, so the same arrays apply regardless of pool mechanics.
Brings 65 commits of noise-modelling work onto noise-plus-plus,
including:
- Per-pool linear noise model (market_linear) and Michaelis-Menten
  with observed competitor TVL (mm_observed)
- Noise-only lp_fee_revenue_usd reporting so optimisers can't monetise
  pool decay through sloshing arb volume
- In-range gate on noise volume; protocol fee split applied to noise
  fees; blessed-arb experiment knob
- Balancer noise model support
- Subsampling of all minute-resolution dynamic input arrays
  (fees/gas_cost/arb_fees/lp_supply) to match arb_frequency

Conflicts resolved (4 files):
- pyproject.toml, setup.py: drop gdown, keep Historic-Crypto casing
- reclamm.py: take the _resolve_noise_inputs / STE structure from
  noise-plus-plus, re-add mm_observed plus competitor_tvl_array
  threading at the call sites
- reclamm_reserves.py: keep _BLESSED_ARB module flag and the
  noise_fee_income initializer; STE gates and noise-model dispatch
  are otherwise complementary

Test failures pre-existing on noise-modelling and not introduced by
this merge: 13 baseline/forward-pass tolerance failures
(3 already fixed downstream on training-pipeline as 'update
forward_pass_test_2 baseline after optimal_n_pool_arb relative
invariant change'). Five lp_fee_revenue tests still fail post-merge
because the test fixtures don't configure a noise model — addressed
in a follow-up commit.
lp_fee_revenue_usd is noise-only by design (so optimisers can't
monetise sloshing arb volume through a decaying pool). The five
tests below were written when lp_fee_revenue_usd reported total
inbound fees, and stopped passing once the semantic changed.

Add a small _mm_observed_noise_kwargs helper that supplies constant
noise_base and competitor_tvl arrays (the simplest noise model to
wire — two scalars vs. tsoukalas' volatility + noise_params). With
noise_base=13.8 and K=1e7, a $1M pool produces ~$0.20/step of noise
fee income at 0.3% fees — positive and detectable without dominating
the pool dynamics.

Tests updated:
- test_fee_revenue_positive_on_price_jump
- test_higher_fees_more_revenue
- test_protocol_split_reduces_lp_revenue
- test_dynamic_inputs_fee_revenue
- test_lp_supply_with_fee_revenue (uses K=1e10 so noise volume
  scales ~linearly with pool TVL, preserving the doubling check)
…itting penalty

Optuna's writer now persists train_objective, test_objective, and
continuous_test_metrics as list[dict] with the full metric set, matching the
shape save_multi_params produces for cma_es and bfgs. Rich metric dicts are
captured in the optuna objective callback via set_user_attr.

--overfitting-penalty is wired into cma_es_settings and applied inside
eval_single using the same train-vs-val gap formula optuna uses, with val
evaluation points drawn from the validation period. Penalty now factors into
the run_fingerprint, so the 3 penalty variants of each (token, objective, TVL)
produce distinct run_*.json files.
Move experiments/{fetch_competitor_tvl,run_mm_noise,tune_reclamm_calibrated_noise}.py
into scripts/ to live alongside run_full_sweep.sh and run_final_sims.py.
Update scripts/run_full_sweep.sh and scripts/run_period_sweep.sh to the new
paths.

Add RECLAMM_TRAINING.md at repo root — end-to-end guide covering data pull,
grid build, MM noise model, training sweep, selection and plot stages, plus
extending the calibration set to a new pair.
Carries the minimum data to run scripts/run_final_sims.py and
scripts/run_pr_sweep.py for AAVE/ETH and COW/ETH without re-running the
training sweep. Binance price parquets are not included; pull them with
scripts/download_data.py.

- results/mm_noise/{model.npz,meta.json}: frozen MM noise model artifact
- results/competitor_tvl/competitor_tvl.npz: DeFi Llama competitor TVL
- results/full_sweep/*_penalty1.0_*.json: per-config sweep summaries for the
  6 winning (pair, TVL) configurations
- results/run_<hash>.json: trial trajectories for those 6 winners, read by
  load_reclamm_results in run_final_sims.py
The PR-sweep entry point referenced by the training guide
(RECLAMM_TRAINING.md) and the plot-reproduction runbook
(RECLAMM_PLOTS.md, added in dd20627).
- Skip the BinanceDataDumper exchangeInfo ticker validation: the endpoint is
  geo-switched to binance.us for US IPs, which hides pairs (e.g. RPLUSDT)
  that exist in the binance.vision archive but aren't listed on Binance.US,
  making download_data.py report 'no data' for those tokens.
- Convert the reindex grid to milliseconds explicitly in
  forward_fill_ohlcv_data: int64 cast of a DatetimeIndex is unit-dependent
  on pandas>=3, which collapsed every timestamp to the same value and made
  update_historic_data fail its minute-grid check for every token.
- Use lowercase 'h' resample alias ('H' removed in pandas 3).

Verified end-to-end: update_historic_data('RPL') completes on pandas 2.3.3
(including with US geo-detection simulated) and pandas 3.0.3.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fix download_data.py for US users and pandas 3
cli.py uses PEP 604 unions (str | None) in signatures, which are evaluated
at import time and raise TypeError on Python 3.9 (the CI version). This
broke collection of all seven tests/noise files. Defer annotation
evaluation with the __future__ import.

Verified on a Python 3.9.x env with .[dev,calibration]: full-suite
collection is clean (2198 tests) and tests/noise passes 176/176.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fix noise_calibration import on Python 3.9
4c9eae7 changed the default protocol_fee_split from 0.0 to 0.25 to match
reClAMM production configuration, but the stored forward-pass baselines
(captured with split=0.0) were not re-captured, failing 12 baseline tests
on any fingerprint that charges fees. Re-capture forward_pass_test_1/2
final values and returns with the production default; weights are
unaffected. Also update test_excludes_training_fields: startDateString
is now intentionally kept in the static dict (needed by the calibrated
noise model, see _TRAINING_ONLY_FIELDS in jax_runner_utils).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Update test baselines for protocol_fee_split=0.25 default
…nboarding skill

- run_final_sims.py: PAIR_CONFIGS entries for btceth (WBTC/WETH pool
  0xa6f548df93de92 from the MM artifact) and boldusdc (median-fallback
  noise); --pair choices derived from PAIR_CONFIGS; optional per-pair
  train/test window overrides (BOLD data starts 2025-07-09, CoinGecko
  free-tier limit) with the trial filter applied per pair; thread the
  --metric flag into select_best_params (was hardcoded).
- scripts/prepare_bold_usdc_data.py: CoinGecko data prep for BOLD
  (liquity-bold-2) + USDC $1-peg rebuild over the union grid.
- run_full_sweep.sh: btceth_5m config line.
- Sweep artifacts: 50-trial returns_over_hodl runs for btceth_5m and
  boldusdc_1m (demo scale; production is 300 trials x 4 objectives).
- .claude/skills/reclamm-pair-onboarding: step-by-step runbook for
  onboarding new pairs (data routes, pool-id lookup and median-fallback
  caveats, sweep command, registration, outputs).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@tiago-hansen
tiago-hansen merged commit 4e2c945 into main Jul 9, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants